在BOS项目中应用shiro框架进行认证
概念:
认证:系统提供的用于识别用户身份的功能,通常登录功能就是认证功能-----让系统知道你是谁??
授权:系统授予用户可以访问哪些功能的许可(证书)----让系统知道你能做什么??
常见的权限控制方式
URL拦截权限控制(此文章使用这个)
底层基于拦截器或者过滤器实现
方法注解权限控制
https://blog.csdn.net/qq_36138324/article/details/80087191
底层基于代理技术实现,为Action创建代理对象,由代理对象进行权限校验
shiro官网:shiro.apache.org
shiro框架的核心功能:
认证、授权、会话管理、加密
shiro框架认证流程
Application Code:应用程序代码,由开发人员负责开发的
Subject:框架提供的接口,代表当前用户对象
SecurityManager:框架提供的接口,代表安全管理器对象
Realm:可以开发人员编写,框架也提供一些,类似于DAO,用于访问权限数据
begin!
第一步:引入shiro框架相关的jar
<!-- 引入shiro框架的依赖 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <!-- <version>1.2.2</version> --> <version>1.3.2</version> </dependency>
第二步:在web.xml中配置spring框架提供的用于整合shiro框架的过滤器
<!-- shiro的过滤器一定要写在在struts2的过滤器之前,否则方法注解配置权限控制的方式将会失效 注意,过滤器的name要复制一遍,去spring的配置文件中添加一个bean name就是这个,否则引发 org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named '<filter-name>abc</filter-name>中间的abc' available --> <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>
启动tomcat服务器,抛出异常:spring工厂中不存在一个名称为“shiroFilter”的bean对象
第三步:在spring配置文件中配置bean,id为shiroFilter
<!-- 配置shiro框架的过滤器工厂对象 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- 注入安全管理器对象 --> <property name="securityManager" ref="securityManager"/> <!-- 注入相关页面访问URL --> <property name="loginUrl" value="/login.jsp"/> <property name="successUrl" value="/index.jsp"/> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <!--注入URL拦截规则 --> <property name="filterChainDefinitions"> <value> /css/** = anon <!-- anon匿名访问,就是可以不用任何权限和认证就能够访问到的资源 --> /js/** = anon <!-- 如若不放行的话登陆界面都进不了,因为css、js、image等等都加载不出来 --> /images/** = anon /validatecode.jsp* = anon /login.jsp = anon /UserAction_login = anon<!-- 下面的perms新的过滤器,检查当前登录用户是否具有staff-list,就是查看取派员的列表这个权限 如果权限不足就跳到上面property的name为unauthorizedUrl的权限不足页面 --> /page_base_staff.action = perms["staff-list"] /* = authc <!-- 其它路径要求都要认证通过才允许访问,否则跳转到property的name为loginUrl页面 --> </value> </property> </bean>
记得一定要写上/login.jsp =anon和/UserAction_login = anon
也就是说登陆相关的路径,否则谁也别想登陆进系统
下面是框架提供的过滤器:
第四步:配置安全管理器
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"/>
第五步:修改UserAction中的login方法,使用shiro提供的方式进行认证操作
/** * 用户登陆 * @return * @throws Exception */ public String login() throws Exception { // 拿到存储到session的验证码 String fwqYzm = (String) ServletActionContext.getRequest().getSession().getAttribute("key"); if (StringUtils.isNotBlank(checkcode) && checkcode.trim().equals(fwqYzm)) {// 验证码成功 //使用shiro框架提供的方式进行认证操作 Subject subject = SecurityUtils.getSubject(); // 获得model中已经封装好的账号密码 String username = super.model.getUsername(); String password = super.model.getPassword(); password = MD5Utils.md5(password); //记得加密验证,因为数据库存储的不是明文 // 创建用户名密码令牌对象 AuthenticationToken token = new UsernamePasswordToken(username,password); // 由于这个方法方法没有返回值,所以需要用try,catch的方式来包裹验证的方法 try { subject.login(token); // 执行登陆 /* * 这个Subject在认证的时候会调用安全管理器对象(Shiro SecurityManager)进行认证 * 但由于安全管理器需要用到Realm,所以需要自己创建一个class实现Realm这个接口 * 不过一般为了简便省事,所以一般直接继承Realm的子类AuthorizingRealm * 这个项目的Realm创建在/Logistics_bos-service/src/main/java/shun/bos/realm/BOSRealm.java */ } catch (UnknownAccountException e ) { this.addActionError("用户未注册"); return LOGIN;// 返回的常量就是小写的"login" } catch (IncorrectCredentialsException e ) { this.addActionError("密码错误!!"); return LOGIN;// 返回的常量就是小写的"login" } catch (LockedAccountException e ) { this.addActionError("该账户不可用~"); return LOGIN;// 返回的常量就是小写的"login" } catch (ExcessiveAttemptsException e ) { this.addActionError("尝试次数超限!!"); return LOGIN;// 返回的常量就是小写的"login" } /** * 通过subject取出user对象,在Realm类里我写了这么一句 * AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName()); * 所以取的出来 */ BcUser user = (BcUser) subject.getPrincipal(); // 将user对象存入session域 ServletActionContext.getRequest().getSession().setAttribute("user", user); return "index"; // shiro框架没有抛出异常说明认证成功 }else {// 验证码失败跳回登录界面 this.addActionError("验证码错误!"); return LOGIN;// 返回的常量就是小写的"login" } } /** * 用户登陆,在没有用shiro框架之前用的方法,作为备份,以防修改出错 * @return * @throws Exception */ public String login_old() throws Exception { // 拿到存储到session的验证码 String fwqYzm = (String) ServletActionContext.getRequest().getSession().getAttribute("key"); if (StringUtils.isNotBlank(checkcode) && checkcode.trim().equals(fwqYzm)) {// 验证码成功 // 由于账号密码已经封装到了model里面了,所以直接传递到service层去校验 boolean result = userService.check(this.model); if (result) { return "index"; }else { this.addActionError("账号或密码错误!"); return LOGIN; } }else {// 验证码失败跳回登录界面 this.addActionError("验证码错误!"); return LOGIN;// 返回的常量就是小写的"login" } }
第六步:自定义realm,并注入给安全管理器
package shun.bos.realm; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import shun.bos.dao.IUserDao; import shun.bos.domain.BcUser; /** * @author czs * @version 创建时间:2018年4月25日 上午11:24:00 * 这个Shiro框架的Subject在认证的时候会调用安全管理器对象(Shiro SecurityManager)进行认证 * 但由于安全管理器需要用到Realm,所以需要自己创建一个class实现Realm这个接口 * 不过一般为了简便省事,所以一般直接继承Realm的子类AuthorizingRealm */ public class BOSRealm extends AuthorizingRealm { @Autowired private IUserDao userDao; @Override /** * 授权方法 */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) { // TODO 自动生成的方法存根 return null; } @Override /** * 认证方法 */ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException { System.out.println("自定义的Realm中的认证方法执行了"); UsernamePasswordToken passwordToken = (UsernamePasswordToken) arg0; // 获得页面输入的用户名和密码 String username = passwordToken.getUsername(); // 根据用户名查询数据库,返回一个user对象,这个对象里面当然就包含了密码 BcUser user = userDao.findUserByUsername(username); if (user == null) { return null; // 页面输入的用户名不存在,调用方出现异常,认证失败 } /** *简单认证信息对象 SimpleAuthenticationInfo(Object principal, Object credentials, String realmName) *这三个参数分别要传入user对象,因为在调用subject.login(token)方法没有异常之后 *将会通过BcUser user = (BcUser) subject.getPrincipal();来取出user对象 *要将user对象存入session域、第二个是对象的密码、最后一个String realmName就传入this.getName()就好 */ AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName()); // 由框架负责比对数据库中的密码和页面输入的密码是否一致 return info; } }
最后修改bean为securityManager的安全管理器,将自定义的realm注入给安全管理器
<!-- 安全管理器对象 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><!-- 这个Shiro框架的Subject在认证的时候会调用安全管理器对象(Shiro SecurityManager)进行认证,也就是当前bean 但由于安全管理器需要用到Realm,所以需要自己创建一个class实现Realm这个接口,这个realm引用下面的bean --> <property name="realm" ref="bosRealm"/> </bean> <!-- 注册realm --> <bean name="bosRealm" class="shun.bos.realm.BOSRealm"/>