Shiro过滤器&标签简介
判断 sesion中是否有 user 判断账户密码是否正确
(1)分析
需要过滤器控制 没有权限下访问链接
需要标签控制 没有权限下 界面元素的隐藏
》anon代表不认证也可以访问,通常对静态资源进行放行
》authc代表必须通过认证才可以访问,通常对动态资源(controller,jsp页面)进行拦截,如果用户没有认证,Shiro会自动跳转到login.jsp页面
Shiro登陆认证-判断session中的user
(1)过滤器
在项目中使用认证过滤器拦截资源(该拦截的拦截,该放行的放行)
(2)authc认证过滤器
必须认证才能访问,如果未认证跳登录页
(3)anon过滤器
放行,不需要拦截认证
(4)拦截路径问题
/*
/**
**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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--shiro搭建2 2.3 配置shiroFilter 认证或授权逻辑处理对象-->
<!--注意:这里的shiroFilter必须和web.xml的filter-name保持一致-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--注入SecurityManager-->
<property name="securityManager" ref="securityManager"/>
</bean>
<!--2.1.创建 SecurityManager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--注入Realm-->
<property name="realm" ref="authRealm"/>
</bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--注入SecurityManager-->
<property name="securityManager" ref="securityManager"/>
<property name="filterChainDefinitions">
<value>
<!-- 如果请求提静态资源 anon 不要求session中有user,直接可以访问-->
/css/**=anon
/img/**=anon
/plugins/**=anon
<!-- 如果请求login或者regist方法 anon-->
/system/user/login=anon
/system/user/regist=anon
<!-- 如果除以上之后的xxx 要求登录 authc查询session中是否有user-->
/**=authc
</value>
</property>
<!--如果 session就是没有user,表示未登录,页面转到login.jsp -->
<property name="loginUrl" value="/login.jsp"/>
</bean>
<!--使用自己定义的匹配器-->
<bean id="credentialsMatcher2" class="com.xy.utils.CustomCredentialsMatcher">
</bean>
<!--2.2.创建Realm-->
<bean id="authRealm" class="com.xy.shiro.AuthRealm">
<property name="credentialsMatcher" ref="credentialsMatcher2"/>
</bean>
</beans>
Shiro登录认证-用户密码判断
Shiro登陆认证三种结果
(1)正确
无异常
User user = (User)subject.getPrincipal()//获取user对象
(2)用户不存在
程序抛出UnknownAccountException异常
(3)用户密码出错
程序抛出IncorrectCredentialsException异常
Shiro登陆认证login方法
(1) 获取Subject对象
Subject subject = SecurityUtils.getSubject();
(2)调用Subject的认证方法 : login 本质:AuthRealm的认证方法
subject.login(token);
(3)登录的结果
1、 正确
2、用户不存在
3、用户密码出错
UserController
userController通地subject连接到securitymanager,再由securityManager去调用realm
@RequestMapping(path = "/login")
public String login(String email, String password, HttpSession session, HttpServletRequest request){
l.info("login email="+email);
l.info("login password="+password);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(email, password);
try {
subject.login(usernamePasswordToken);
l.info("login 登录成功");
User user = (User) subject.getPrincipal();
l.info("login user="+user);
session.setAttribute("user",user);
List<Module> menus = iModuleService.findModulesByUser(user);
session.setAttribute("menus",menus);
l.info("login menus "+menus);
//跳到主页
return "redirect:/home/toMain.do";
} catch (IncorrectCredentialsException e){//密码出错
e.printStackTrace();
l.info("密码不对");
request.setAttribute("error","邮箱或者密码不对");
return "forward:/login-shiro.jsp";
}
}
AuthRealm
//不需要再添加@Component 因为在xml中使用bean标签配置了
//AuthorizingRealm 认证(登录账号密码)和 授权(查有什么权限)
public class AuthRealm extends AuthorizingRealm {
private Logger l = LoggerFactory.getLogger(this.getClass());
@Autowired
private IModuleService iModuleService;
@Autowired
private IUserService userService;
//spring是通过反射调用无参构造函数
public AuthRealm(){
l.info("AuthRealm 无参构造函数执行了");
}
//授权(查有什么权限)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
l.info("AuthRealm doGetAuthorizationInfo 函数执行了");
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
if (user!=null){
//查询权限
List<Module> modules = iModuleService.findModulesByUser(user);
SimpleAuthorizationInfo info =new SimpleAuthorizationInfo();
for (Module m:modules){
l.info("AuthRealm AuthorizationInfo m="+m.getName());
info.addStringPermission(m.getName());
}
return info;
}
return null;
}
//认证(登录账号密码)
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
l.info("AuthRealm doGetAuthenticationInfo 函数执行了");
//响应login方法和getPrincipal方法的调用
UsernamePasswordToken usernamePasswordToken= (UsernamePasswordToken) token;
String email = usernamePasswordToken.getUsername();
l.info("doGetAuthenticationInfo --"+email);
//调service读dao
User user = userService.findUserByEmail(email);
l.info("doGetAuthenticationInfo --"+user);
if (user == null) {
//用户不存在
return null;//-->系统会将null转成UnknownAccountException抛出
} else {
//用户存在的
//AuthenticationInfo 返回给 User user = (User) subject.getPrincipal();
/**
* 参数一:principal,存放用户登录信息,subject.getPrincipal()获取
* 参数二:数据库的密码
* 参数三:realm的别名,只有在多个Realm的时候才会用,一般不用
*/
AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), "");
return info;
}
}
}
MD5Utils
public class MD5Utils {
//
public static String stringToMD5(String plainText) {//参1 明文
byte[] secretBytes = null;
try {
secretBytes = MessageDigest.getInstance("md5").digest(
plainText.getBytes());//对明文的字节进行摘要
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("没有这个md5算法!");
}
//16或者32
String md5code = new BigInteger(1, secretBytes).toString(16);
//补0 一个字节转两位的16进制
for (int i = 0; i < 32 - md5code.length(); i++) {
md5code = "0" + md5code;
}
return md5code;
}
}
@Test
publi
c void stringToMD5() {
String result = MD5Utils.stringToMD5("123");
System.out.println(result);
//md5(123)= 202cb962ac59075b964b07152d234b70
//202cb962ac59075b964b07152d234b70
}
//shiro也集成常用的加密的算法md5,sha-1
@Test
public void test02() {
Md5Hash md5Hash = new Md5Hash("123");//参1 传入明文
System.out.println(md5Hash.toString());
//202cb962ac59075b964b07152d234b70
}
凭证匹配器-加盐加密
(1)为什么要加盐
如果只是单纯使用md5或sha1进行加密,容易被人利用"彩虹表"撞库来破解密码,导致密码不安全!这时可以进行加盐加密来解决。
演示: 加盐
(2)shiro加盐加密
1)编写代码对密码加盐加密
//原来保存用户使用的密码是明文,现在需要对它进行加密
if(user.getPassword()!=null){
Md5Hash md5Hash = new Md5Hash(user.getPassword(),user.getEmail());//参1 明文 参2 盐
user.setPassword(md5Hash.toString());
}
2)编写自定义凭证匹配器
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
private Logger l = LoggerFactory.getLogger(this.getClass());
//将密码加密成密文,但需要使用账号作盐
//subject.login(token) 表示的页面提交的密码 123 -> 密文1
//info 调relam 表示的是数据中的密码 密文2
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//token获取账号密码
UsernamePasswordToken t = (UsernamePasswordToken) token;
String email = t.getUsername();
//密码是char[] '1','2','3' => "123" new String(char[])
String pwd1 = new String(t.getPassword());
l.info("doCredentialsMatch pwd1 "+pwd1);
//加密
Md5Hash md5Hash = new Md5Hash(pwd1, email);//参1 传入明文 参2盐
pwd1 = md5Hash.toString();
l.info("doCredentialsMatch pwd1 "+pwd1);
//取数据库的密码 此时不用再加密码,因为它就是密文
String pwd2 = (String) info.getCredentials();
l.info("doCredentialsMatch pwd2 "+pwd2);
if (pwd1.equals(pwd2)) {
return true;//密码正确
} else {
return false;//密码不正确
}
}
}
3)在applicationContext-shiro.xml,添加自定义凭证匹配器
<!--使用自己定义的匹配器-->
<bean id="credentialsMatcher2" class="com.wzx.web.utils.CustomCredentialsMatcher">
</bean>
<!--2.2.创建Realm-->
<bean id="authRealm" class="com.wzx.web.shiro.AuthRealm">
<property name="credentialsMatcher" ref="credentialsMatcher2"/>
</bean>
LoginOut
(1)之前我们再退出时候,已经实现了退出后销毁session。
//删除Shiro的session(底层:删除session数据)
Subject subject = SecurityUtils.getSubject();
subject.logout();
@RequestMapping(path = "/loginOut-shiro", method = {RequestMethod.GET, RequestMethod.POST})
public String loginOutShiro(){
//删除session中的用户信息
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login-shiro.jsp";
}
shiro授权 介绍
(1)什么是授权?
授权,也叫做授权访问校验
登陆认证后,系统校验用户是否有权限访问资源,就叫授权。
(2)如何实现授权?分为以下两个步骤
》1 登陆认证成功后,获取用户的权限 (获取权限)
》2 访问资源时候,进行授权校验:用访问资源需要的权限去用户权限列表查找,如果存在,则有权限访问资源。(权限拦截)
(3)授权校验有几种方式
1) 硬编码方式(拦截方法)(非Web应用,Web应用)
2) 过滤器配置方式(拦截url)(Web应用)
3) 注解方式(拦截方法)(Web应用)
4) shiro提供的标签((拦截页面元素:按钮,表格等))(Web应用)
第一种访问代码(不推荐) 硬编码
(1)虽然看不到用户管理,但直接输入地址,也可以访问
因此,需要给用户管理添加权限
(2)判断权限方法调用
Subject subject = SecurityUtils.getSubject();
subject.checkPermission(“用户管理”);
会调用:com.wzx.web.shiro.AuthRealm#doGetAuthorizationInfo
(3)如果没有权限则出现报错
第二种访问代码
XML配置方式实现权限管理(推荐)
(1)通过XML配饰方式实现需求: 有用户管理的权限,才可以访问; 否则拒绝访问。
(2)原理:shiro过滤器
拦截请求时,获取授权
(3)在applicationContext-shiro.xml
<!-- 给url配置权限--> /company/toList.do=perms["企业管理"] /system/user/toList.do=perms["用户管理"]
(4)可以指定无权限页面提示
<property name="unauthorizedUrl" value="/unauthorized.jsp"/>
第三种访问代码
shiro授权 注解方式实现
(1)通过注解配置方式实现需求: 有用户管理的权限,才可以访问; 否则拒绝访问。(先注释掉xml配置部分)
(2)实现步骤
》1 在applicationContext-shiro.xml中开启shiro注解支持
<!-- @RequiredPermission-->
<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>
》2 开启Aop自动代理(已经完成)
springmvc.xml
<aop:aspectj-autoproxy/>
》3 在controller中使用
@RequiresPermissions("")注解
@RequiresPermissions("企业管理")
第四种访问方式
shiro授权 Shiro标签实现权限管理
(1)新建测试页面
shiro-test.jsp
不同账号进行访问
》1 在页面引入shiro标签 ,类似c标签
<%@ taglib prefix=“shiro” uri=“http://shiro.apache.org/tags” %>
》2 使用shiro标签 给name赋值,赋上什么值就表示需要什么权限
<%-- 标签会查询权限,如果没有权限不报错,将页面内容进行隐藏,反之显示页面内容--%>
<shiro:hasPermission name="企业管理">
<a href="">企业管理</a>
</shiro:hasPermission>
<shiro:hasPermission name="用户管理">
<a href="">用户管理</a>
</shiro:hasPermission>
<shiro:hasPermission name="日志管理">
<a href="">日志管理</a>
</shiro:hasPermission>
shiro认证和授权小结
1、认证: subject.login(token);
subject调用login方法进行认证,login方法使用securityManager安全管理器的Authentication认证器认证,Authentication认证器找我们编写AuthorizingRealm的子类进行效验通过则返回AuthenticationInfo
对象,否则就抛异常。
2、授权: subject.checkPermission(“权限名称”)
原理:授权一般在 登陆认证成功后自动查询用户所有权限,并添加到shiro中,然后访问资源时候,进行授权校验:用访问资源需要的权限去用户权限列表查找,如果存在,则有权限访问资源。否则跳转到指定的没有权限提示页面。
流程:通过subject获取登录的用户对象,如果对象不为空,则根据用户查询对应的权限,创建一个SimpleAuthorizationInfo
,然后将权限存到SimpleAuthorizationInfo
中,并且返回这个对象。