Shiro框架-----用来实现认证和授权的操作
Posted lanto
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shiro框架-----用来实现认证和授权的操作相关的知识,希望对你有一定的参考价值。
Shiro框架
1.概念:shiro : 是一个权限管理控制框架。主要提供了认证与授权的操作。
认证:用户得登录成功后才能访问页面。 (这时是可以访问所有页面)
授权:授权访问资源,是指用户认证成功的情况下,再判断模块(权限)来访问 可访问的页面。 (这时是只能访问又权限的页面)
2.shiro 和 spring security的比较
①shiro比spring security简单
②shiro需要和spring整合,而spring security不需要
③spring security功能更加的强大
3.shiro的核心实现是---过滤器(拦截或过虑请求),用于用户访问页面(URL)时进行权限控制
4.shiro的主要功能模块有:
Authentication:身份认证/登录,------核心功能
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限 --------核心功能
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中
Cryptography:加密,保护数据的安全性,
Web Support:Web支持
Caching:缓存
Concurrency:shiro支持多线程应用的并发验证
记住一点,Shiro不会去维护用户和维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可
5.shiro的三大组件
Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,
保存用户信息,负责认证和授权方法调用,只要调了方法就会到SecurityManager
SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;它管理着所有Subject;可以看出它是Shiro的核心,负责认证和授权的管理
Realm:域,连接数据库获取认证和授权信息的桥梁,Shiro从Realm获取安全数据(如用户、角色、权限);
也就是说对于我们而言,最简单的一个Shiro应用:
1、应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
2、我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。
从以上也可以看出,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。
6.shiro的环境搭建
①项目添加shiro依赖 (父项目已经添加) (shiro通常是一个项目的一部分)
<!--shiro-->
<!--shiro和spring整合-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!--shiro核心包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
②配置web.xml
<!--Spring整合Shiro的过滤器
作用: 接收所有需要交给Shiro过滤的请求
过滤器的名称: filter-name就是Spring的bean的id
-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
③配置applicationContext-shiro.xml, Spring整合shiro
<?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">
<!--1.Shiro处理认证授权逻辑的对象-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--安全管理器-->
<property name="securityManager" ref="securityManager"/>
</bean>
<!--2.配置安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--配置Realm-->
<property name="realm" ref="myRealm"/>
</bean>
<!--3.配置Realm-->
<bean id="myRealm" class="cn.itcast.web.shiro.AuthRealm"/>
</beans>
④创建自定义realm类:AuthRealm,继承AuthorizingRealm
public class AuthRealm extends AuthorizingRealm{
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}
7.shiro的使用
前引:
一:与认证(登录)相关的过滤器的使用
过滤器:(先拦截,在判断)
anon:匿名过滤器,代表不认证(登录)也可以访问该请求,通过对静态资源进行放行(指css,js,图片都没有了)
authc:认证过滤器,代表必须认证成功才可以访问该资源,如果认证不成功或没有认证,会自动跳转到login.jsp页面(自带的),但是可以修改默认登录页面,用<property name="loginUrl" value="/login.jsp"/>
用法:
/make/**=anon
/** = authc
anon一定要放在authc前,/** = authc表示拦截所有的请求,而在其上面的/make/**=anon表示这个放行不拦截
该拦截的资源用authc,该放行的资源用anon
又由于静态资源也会被拦截,所以anon主要用来给静态资源放行(css,js,图片等)
/login.do因为是登录请求,必须使用anon过滤器放行!!!否则无法登录啦!
二与授权相关的过滤器
第一部分:
第一步:applicationContext-shiro.xml添加认证过滤器
<!--1.Shiro处理认证授权逻辑的对象--> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!--安全管理器--> <property name="securityManager" ref="securityManager"/> <!--设置Shiro的过滤器--> <!-- 过滤的路径问题: /index.jsp : 准确拦截/index.jsp页面 /user/* : 模糊匹配,拦截user根目录下的所有资源(代表一级目录) /user/** : 模糊匹配,拦截user目录下的所有资源(代表任意级目录) 1. 认证相关的过滤器 anon: 匿名过滤器,不用认证也可以访问该资源(必须放在authc的前面) authc:认证过滤器,必须认证成功才可以访问该资源,如果认证不成功,会自动跳转到login.jsp页面(可以通过loginUrl属性更新登录页面) --> <property name="filterChainDefinitions"> <value> /css/**=anon /img/**=anon /make/**=anon /plugins/**=anon /login.do=anon /**=authc </value> </property> <!--修改shrio默认登录页面--> <property name="loginUrl" value="/login.jsp"/> </bean>
第二步:修改LoginController,通过shiro实现登陆认证
/** * 登录方法 * 1)URL: http://localhost:8080/login.do * 2)参数:email=eric&password=123 * 3)返回:/WEB-INF/pages/home/main.jsp */ @RequestMapping("/login") public String login(String email,String password){ //1.判断是否为空 if(StringUtils.isEmpty(email) || StringUtils.isEmpty(password)){ request.setAttribute("error","邮箱和密码不能为空"); //手动转发到login.jsp页面 return "forward:/login.jsp"; } //-----------------------------------------------------一以下是正常做法-------------------------------------------------------- /*//2.判断email是否存在 User loginUser = userService.findByEmail(email); //3 email不存在,提示"用户名不存在" if(loginUser==null){ request.setAttribute("error","用户名不存在"); //手动转发到login.jsp页面 return "forward:/login.jsp"; } //4 email存在,继续判断password是否正确 if(loginUser.getPassword().equals(password)){ //5. password正确,登录成功,保存用户数据到session域中,跳转到主页 session.setAttribute("loginUser",loginUser); //查询当前用户拥有的菜单(模块) List<Module> menuList = moduleService.findModuleByUser(loginUser); //去除重复元素 removeDuplicate(menuList); //菜单必须存入session域 session.setAttribute("menus",menuList); // 路径: /WEB-INF/pages/home/main.jsp return "home/main"; }else{ //6.password不正确,提示"密码错误" request.setAttribute("error","密码错误"); //手动转发到login.jsp页面 return "forward:/login.jsp"; }*/ //---------------------------------------------------以上是正常做法--------------------------------------------------- //使用Shiro的登录认证逻辑 //1.获取Subject对象 Subject subject = SecurityUtils.getSubject(); //2.UsernamePasswordToken 封装需要认证的信息(用户名和密码) UsernamePasswordToken token = new UsernamePasswordToken(email,password); //3.调用Subject的login方法进行认证 //4.判断login方法有无异常,有异常代表认证失败,无异常代表认证成功 try { subject.login(token); //无异常代表认证成功 //5.从Shiro的Subject中获取登录用户对象: subject.getPrincipal() User loginUser = (User)subject.getPrincipal(); //把登录用户对象存入session域 (注意:这里存入的loginUser对象,不是给Shiro使用的,是给我们自己业务使用的) session.setAttribute("loginUser",loginUser); //查询当前用户拥有的菜单(模块) List<Module> menuList = moduleService.findModuleByUser(loginUser); //去除重复元素 //若是已经在sql语句那里加了distinct去重,就不需要这个了 removeDuplicate(menuList); //菜单必须存入session域 session.setAttribute("menus",menuList); //跳转到主页 return "home/main"; } catch (UnknownAccountException e) { //UnknownAccountException: 该异常代表用户名不存在 request.setAttribute("error","Shrio:用户名不存在"); return "forward:/login.jsp"; } catch (IncorrectCredentialsException e) { //IncorrectCredentialsException: 该异常代表密码错误 request.setAttribute("error","Shrio:密码输入有误"); return "forward:/login.jsp"; } catch (Exception e) { //其他异常 request.setAttribute("error","Shrio:服务器错误"); return "forward:/login.jsp"; } }
第三步:编写AuthRealm,实现登陆认证
/** * 自定义Realm */ public class AuthRealm extends AuthorizingRealm{ @Autowired private UserService userService; //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行授权方法..."); return null; } //认证 (subject.login(token)方法固定执行doGetAuthenticationInfo方法) @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //System.out.println("执行认证方法..."); //1.判断用户名是否存在 //token传给了AuthenticationToken UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken; String email = token.getUsername(); User loginUser = userService.findByEmail(email); if(loginUser==null){ //用户名不存在 //我们只需要return null即可,Shiro底层判断为null则抛出UnKnowAccountException异常 return null; } //2.返回数据库保存密码给Shiro,让Shiro判断密码是否正确 /** * Shiro底层获取SimpleAuthenticationInfo的参数,例如获取password判断和用户输入的密码是否一致 * 1)如果密码不一致,抛出IncorrectCredentialsException异常 * 2)如果密码一致, 把principal存入session域 */ //SimpleAuthenticationInfo: 封装认证数据对象 /** * 参数一: principal 登录用户对象,用于Subject.getPrincipal()方法获取 * 参数二: password 数据库的密码 * 参数三: realm的别名, 在多个Realm的情况下使用,用于区分用户表的。只有一张用户表不需要别名 */ return new SimpleAuthenticationInfo(loginUser,loginUser.getPassword(),""); } }
总结:
Shiro登录认证的流程是怎样的?
AuthRealm的认证方法,编写判断用户名和返回数据库密码的逻辑,从数据库获得数据
8.加密
<!--创建Shiro自带的凭证匹配器--> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!--指定需要加密的算法名称--> <property name="hashAlgorithmName" value="md5"/> </bean>
第二步:对数据库密码进行md5加密(select md5(123);),再直接修改数据库的用户密码
请简单说出Shiro加密判断的执行流程
Subject.login(token) (未加密数据) -> AuthRealm的认证方法->返回数据库密码->先把token密码使用算法加密,再和数据库的密码匹配
步骤
1)编写代码对密码加盐加密,查看结果,把数据库里面的用户密码替换掉
public class Demo { public static void main(String[] args) { //1.原密码 String password = "123"; //2.盐 String salt = "lw@export.com"; //3.加盐加密 /** * 参数一:原密码 * 参数二:盐 * 参数三(可选):加密次数,默认1次 */ Md5Hash md5Hash = new Md5Hash(password,salt); System.out.println(md5Hash.toString()); } }
2)编写自定义凭证匹配器
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher{ /** * 完成密码判断的逻辑 * @param token 包含了用户输入的密码 * @param info 包含了数据库的密码 * @return 密码判断结果 * true: 代表数据库的密码和用户输入的密码一致的 * false:代表数据库的密码和用户输入的密码不一致的 */ //打个do就能出来这个方法 @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { //1.获取用户输入的密码 UsernamePasswordToken userToken = (UsernamePasswordToken)token; //UsernamePasswordToken 是用来封装用户名和密码,用户名用字符串封装,密码用字符数组封装 //userToken.getPassword()[‘1‘,‘2‘,‘3‘] String userPassword = new String(userToken.getPassword()); // [‘1‘,‘2‘,‘3‘] //获取用户邮箱 String email = userToken.getUsername(); //2.对用户输入的密码进行加盐加密 Md5Hash md5Hash = new Md5Hash(userPassword,email); String encodePwd = md5Hash.toString(); //3.获取数据库的密码 String dbPwd = (String)info.getCredentials(); //4.判断数据库的密 码和第二步产生的密码是否一致,一致返回true,不一致返回false return dbPwd.equals(encodePwd); } }
3)在applicationContext-shiro.xml,添加自定义凭证匹配器
<!--3.配置Realm--> <bean id="myRealm" class="cn.itcast.web.shiro.AuthRealm"> <!--配置凭证匹配器--> <property name="credentialsMatcher" ref="credentialsMatcher"/> </bean> <!--创建自定义凭证匹配器--> <bean id="credentialsMatcher" class="cn.itcast.web.shiro.CustomCredentialsMatcher"/>
4)测试
拓展注意:
1.添加用户的时候要让用户的密码加密,故再service实现类里面写添加用户的方法是的业务逻辑是这样的
@Override public void save(User user) { //生成主键 user.setId(UUID.randomUUID().toString()); //对密码加盐加密 Md5Hash md5Hash = new Md5Hash(user.getPassword(),user.getEmail()); user.setPassword(md5Hash.toString()); userDao.save(user); }
2.Shiro的登录注销代码实现
@RequestMapping("/logout") public String logout(){ //删除session的登录数 session.removeAttribute("loginUser"); session.removeAttribute("menus"); //Shiro登录注销(底层:删除之前Shiro存入的session的key) //防止浏览器记住登录认证 Subject subject = SecurityUtils.getSubject(); subject.logout(); return "redirect:/login.jsp"; }
第二部分:
-
登陆认证成功后,获取用户的权限 (获取权限),就相当于买票
-
访问资源时候,进行授权校验:用访问资源需要的权限去用户权限(模块)列表查找,如果存在,则有权限访问资源。(权限拦截)。就相当于售票
.获取权限
实现自定义realm的doGetAuthorizationInfo()方法,返回用户已经具有的权限。
//授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //System.out.println("执行授权方法..."); //1、获取当前登录用户拥有的权限 //从Subject获取当前登录用户 Subject subject = SecurityUtils.getSubject(); User loginUser = (User)subject.getPrincipal(); List<Module> moduleList = moduleService.findModuleByUser(loginUser); //2、把当前登录用户的权限告诉Shiro框架 //2.1 创建授权对象 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //2.2 把模块存入授权对象 if(moduleList!=null && moduleList.size()>0){ for(Module module:moduleList){ if(!StringUtils.isEmpty(module.getName())) { //往授权对象存入授权信息(授权信息存入什么字符串? 字符串必须唯一的) info.addStringPermission(module.getName()); } } } return info; }
.授权校验
shiro其实提供了四种方式实现了权限校验:硬编码,过滤器,注解方法,jsp标签。主要掌握注解方法,jsp标签
1)注解方法
<!--====开启Shiro的注解====--> <!-- Shiro注解借助了Spring的AOP来实现授权校验 --> <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自动代理(已经完成)
<!--6. 开启Aop自动代理--> <aop:aspectj-autoproxy/>
3.在controller中使用@RequiresPermissions(“”)注解
2)再页面上使用jsp标签
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>测试Shiro的jsp标签</title> </head> <body> <shiro:hasPermission name="企业管理"> <a href="">企业管理</a> </shiro:hasPermission> <hr/> <shiro:hasPermission name="用户管理"> <a href="">用户管理</a> </shiro:hasPermission> </body> </html>
最后,总结一下
1.shiro的关键代码:
认证: subject.login(token);
授权: subject.checkPermission("");
2.认证和授权的流程
以上是关于Shiro框架-----用来实现认证和授权的操作的主要内容,如果未能解决你的问题,请参考以下文章