第1章 shiro与spring web项目整合
1.1shiro与springweb项目的整合
shiro 与 springweb 整合在”基于url拦截实现的工程”基础上整合,基于 url拦截实现的工程技术架构是springmvc+mybatis ,整合注意两点 :
1、shiro 与 spring整合
2、加入shiro对web应用的支持
1.2 加入shiro的jar包
- shiro-spring(依赖web)
- shiro-web
- shiro-core
1.3 web.xml添加shiro filter
<!-- shiro过虑器,DelegatingFilterProxy通过代理模式将spring容器中的bean和filter关联起来 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!-- 设置true由servlet容器控制filter的生命周期 -->
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<!-- 设置spring容器filter的bean id,如果不设置则找与filter-name一致的bean-->
<init-param>
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
1.4 applicationContext-shiro.xml
<!-- Shiro 的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
<property name="loginUrl" value="/login.action" />
<!-- 通过unauthoeizedUrl 指定没有权限操作时跳转页面 -->
<property name="unauthorizedUrl" value="/refuse.jsp" />
<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
<property name="filterChainDefinitions">
<value>
<!-- 退出拦截,请求logout.action执行退出操作 -->
/logout.action = logout
<!-- 无权访问页面 -->
/refuse.jsp = anon
<!-- roles[XX]表示有XX角色才可访问 -->
/item/list.action = roles[item],authc
<!--对静态资源的匿名访问-->
/js/** anon
/images/** anon
/styles/** anon
/validatecode.jsp anon
/item/* authc
<!-- user表示身份认证通过或通过记住我认证通过的可以访问 -->
/** = authc
</value>
</property>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm" />
</bean>
<!-- 自定义 realm -->
<bean id="userRealm" class="cn.ty.ssm.realm.CustomRealm1"></bean>
1.5 静态资源
对静态资源设匿名访问:
<!--对静态资源的匿名访问-->
/js/** anon
/images/** anon
/styles/** anon
1.6 登录
1.6.1 原理
使用FormAuthenticationFilter
过滤器实现,原理如下:
- 将用户没有认证 时,请求loginurl进行认证。用户身份和用户密码提交到loginurl。
- FormAuthenticationFilter拦截住取出request中的username和password(两个参数名称是可以配置的)。
- FormAuthenticationFilter调用realm传入一个token(username和password)。
- realm认证时根据username查询用户信息(在Activeuser中存储,包括menus、userid、usercode、username)。
- 如果查询不到,realm返回null。
1.6.2 登录页面
由于FormAuthenticationFilter 的用户身份和密码的input的默认值(username和password),修改页面的账号和密码的input的名称为username和password。
1.6.2 实现
// 用户登陆提交
@RequestMapping("/login")
public String loginsubmit(Model model, HttpServletRequest request)
throws Exception {
// shiro在认证过程中出现错误后将异常类路径通过request返回
String exceptionClassName = (String) request
.getAttribute("shiroLoginFailure");
if(exceptionClassName!=null){
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
throw new CustomException("账号不存在");
} else if (IncorrectCredentialsException.class.getName().equals(
exceptionClassName)) {
throw new CustomException("用户名/密码错误");
} else if("randomCodeError".equals(exceptionClassName)){
throw new CustomException("验证码错误");
} else{
throw new Exception();//最终在异常处理器生成未知错误
}
}
return "login";
}
1.6.3 认证拦截过滤器
在applicationContext-shiro.xml
/** = authc
1.7 退出
不用我们去实现退出,只要去访问一个退出的url(该url是可以不存在的),由LogoutFilter拦截住,消除Session
<property name="filterChainDefinitions">
<value>
<!-- 退出拦截,请求logout.action执行退出操作 ,放到最上面,shiro去清除session
-->
/logout.action = logout
</value>
</property>
1.8认证信息在页面显示
1.8.1 修改realm设置完整认证信息
从数据库查询 用户信息,将用户菜单、usercode、username等设置在SimpleAuthenticationInfo中
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
// token是用户输入的用户名和密码
// 第一步从token中取出用户名
String userCode = (String) token.getPrincipal();
// 第二步:根据用户输入的userCode从数据库查询
SysUser sysUser = null;
try {
sysUser = sysService.findSysUserByUserCode(userCode);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// 如果查询不到返回null
if(sysUser==null){//
return null;
}
// 从数据库查询到密码
String password = sysUser.getPassword();
//盐
String salt = sysUser.getSalt();
// 如果查询到返回认证信息AuthenticationInfo
//activeUser就是用户身份信息
ActiveUser activeUser = new ActiveUser();
activeUser.setUserid(sysUser.getId());
activeUser.setUsercode(sysUser.getUsercode());
activeUser.setUsername(sysUser.getUsername());
//..
//根据用户id取出菜单
List<SysPermission> menus = null;
try {
//通过service取出菜单
menus = sysService.findMenuListByUserId(sysUser.getId());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//将用户菜单 设置到activeUser
activeUser.setMenus(menus);
//将activeUser设置simpleAuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
activeUser, password,ByteSource.Util.bytes(salt), this.getName());
return simpleAuthenticationInfo;
}
1.8.2 将认证信息在首页显示
由于session 由shiro管理,需要修改首页的controller方法,将session中的数据通过model传到页面。
//系统首页
@RequestMapping("/index")
public String index(Model model)throws Exception{
//主体
Subject subject = SecurityUtils.getSubject();
//身份
ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
model.addAttribute("activeUser", activeUser);
return "/index";
}
1.9 授权过滤器的测试
1.9.1 使用PermissionsAuthorizationFilter
在applicationContext-shiro.xml中配置url所对应的权限。
测试流程
1、在applicationContext-shiro.xml中配filter规则
/items/queryItems.action = perms[item:query]
2、用户在认证通过后,请求/items/queryItems.action
3、被PermissionsAuthorizationFilter拦截,发现需要”item:query”权限
4、PermissionsAuthorizationFilter 调用realm中的doGetAuthorizationInfo
5、PermissionsAuthorizationFilter 对item:query和从realm中获取 的权限进行对比,如果”item:query”在realm返回的权限列表中,授权通过。
1.9.2 创建refuse.jsp
<!-- 通过unauthoeizedUrl 指定没有权限操作时跳转页面 -->
<property name="unauthorizedUrl" value="/refuse.jsp" />
1.9.3 问题总结
1、在applicationContext-shiro.xml
中配置过滤器连接,需要将全部的url和权限对应起来进行配置,比较麻烦,不方便使用。
2、每次授权都需要调用realm查询数据库,对于性能有很大影响,可以通过shiro
缓存来解决。
1.10 shiro 过滤器
过滤器简称 | 对应的java类 |
---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port | org.apache.shiro.web.filter.authz.PortFilter |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl | org.apache.shiro.web.filter.authz.SslFilter |
user | org.apache.shiro.web.filter.authc.UserFilter |
logout | org.apache.shiro.web.filter.authc.LogoutFilter |
- anon:例子/admins/**=anon 没有参数,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,FormAuthenticationFilter是表单认证,没有参数
roles:例子/admins/user/* = roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/*=roles[“admin,guest”],每个参数通过才算通过,相当于hasAllRoles()方法。
perms:例子/admins/user/* = perms[user:add:],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/*=perms[“user:add:,user:modify:*”],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
rest:例子/admins/user/* = rest[user],根据请求的方法,相当于/admins/user/*=perms[user:method] ,其中method为post,get,delete等。
port:例子/admins/user/** = port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString
是你访问的url里的?后面的参数。authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证
ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https
- user:例如/admins/user/**=user没有参数表示必须存在用户, 身份认证通过或通过记住我认证通过的可以访问,当登入操作时不做检查
注:
- anon,authcBasic,auchc,user是认证过滤器,
- perms,roles,ssl,rest,port是授权过滤器
1.11 认证
1.11.1 需求
- 修改realm的doGetAuthenticationInfo从数据库查询用户信息,realm返回的用户信息中包括(md5加密后的串和salt),实现让shiro进行散列串 的校验 。
- 添加凭证匹配器
1.11.2 修改doGetAuthenticationInfo从数据库中获取
//realm的认证方法,从数据库查询用户信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
// token是用户输入的用户名和密码
// 第一步从token中取出用户名
String userCode = (String) token.getPrincipal();
// 第二步:根据用户输入的userCode从数据库查询
SysUser sysUser = null;
try {
sysUser = sysService.findSysUserByUserCode(userCode);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// 如果查询不到返回null
if(sysUser==null){//
return null;
}
// 从数据库查询到密码
String password = sysUser.getPassword();
//盐
String salt = sysUser.getSalt();
// 如果查询到返回认证信息AuthenticationInfo
//activeUser就是用户身份信息
ActiveUser activeUser = new ActiveUser();
activeUser.setUserid(sysUser.getId());
activeUser.setUsercode(sysUser.getUsercode());
activeUser.setUsername(sysUser.getUsername());
//..
//根据用户id取出菜单
List<SysPermission> menus = null;
try {
//通过service取出菜单
menus = sysService.findMenuListByUserId(sysUser.getId());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//将用户菜单 设置到activeUser
activeUser.setMenus(menus);
//将activeUser设置simpleAuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
activeUser, password,ByteSource.Util.bytes(salt), this.getName());
return simpleAuthenticationInfo;
}
1.11.3 设置凭证匹配器
添加凭证匹配器实现md5加密校验。
修改applicationContext-shiro.xml:
<!-- 凭证匹配器 -->
<bean id="credentialsMatcher"
class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5" />
<property name="hashIterations" value="1" />
</bean>
<!-- 自定义 realm -->
<bean id="userRealm" class="cn.itcast.ssm.realm.CustomRealm1">
<property name="credentialsMatcher" ref="credentialsMatcher" />
</bean>
1.12 授权
1.12.1 需求
- 修改realm的doGetAutheticationInfo从数据库查询用户信息,realm返回的用户信息中包括(md5加密后的串和salt),实现让shiro进行散列串 的校验 。
- 使用注解式授权方法
- 使用jsp标签授权方法
1.12.2 doGetAutheticationInfo从数据库查询用户信息
1、将SysService注入到realm中
public class CustomRealm extends AuthorizingRealm {
//注入service
@Autowired
private SysService sysService;
2、调用SysService方法 查询用户
//realm的认证方法,从数据库查询用户信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException ){
// token是用户输入的用户名和密码
// 第一步从token中取出用户名
String userCode = (String) token.getPrincipal();
// 第二步:根据用户输入的userCode从数据库查询
SysUser sysUser = null;
try {
sysUser = sysService.findSysUserByUserCode(userCode);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// 如果查询不到返回null
if(sysUser==null){//
return null;
}
// 从数据库查询到密码
String password = sysUser.getPassword();
//盐
String salt = sysUser.getSalt();
// 如果查询到返回认证信息AuthenticationInfo
//activeUser就是用户身份信息
ActiveUser activeUser = new ActiveUser();
activeUser.setUserid(sysUser.getId());
activeUser.setUsercode(sysUser.getUsercode());
activeUser.setUsername(sysUser.getUsername());
//..
//根据用户id取出菜单
List<SysPermission> menus = null;
try {
//通过service取出菜单
menus = sysService.findMenuListByUserId(sysUser.getId());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//将用户菜单 设置到activeUser
activeUser.setMenus(menus);
//将activeUser设置simpleAuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
activeUser, password,ByteSource.Util.bytes(salt), this.getName());
return simpleAuthenticationInfo;
}
// 用于授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//从 principals获取主身份信息
//将getPrimaryPrincipal方法返回值转为真实身份类型(在上边的doGetAuthenticationInfo认证通过填充到SimpleAuthenticationInfo中身份类型),
ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
//根据身份信息获取权限信息
//从数据库获取到权限数据
List<SysPermission> permissionList = null;
try {
permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//单独定一个集合对象
List<String> permissions = new ArrayList<String>();
if(permissionList!=null){
for(SysPermission sysPermission:permissionList){
//将数据库中的权限标签 符放入集合
permissions.add(sysPermission.getPercode());
}
}
/* List<String> permissions = new ArrayList<String>();
permissions.add("user:create");//用户的创建
permissions.add("item:query");//商品查询权限
permissions.add("item:add");//商品添加权限
permissions.add("item:edit");//商品修改权限
*/ //....
//查到权限数据,返回授权信息(要包括 上边的permissions)
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//将上边查询到授权信息填充到simpleAuthorizationInfo对象中
simpleAuthorizationInfo.addStringPermissions(permissions);
return simpleAuthorizationInfo;
}
1.12.3 开启controller类aop的支持
对 系统中类的方法给用户授权,建议 在controller层进行方法授权。在springmvc.xml中配置shiro注解支持,可在controller方法中使用shiro注解配置权限:
<!-- 开启aop,对类代理 -->
<aop:config proxy-target-class="true"></aop:config>
<!-- 开启shiro注解支持 -->
<bean
class="
org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
1.12.4 在controller方法中添加注解
//商品信息方法
@RequestMapping("/queryItems")
@RequiresPermissions("item:query")//执行queryItems需要"item:query"权限
public ModelAndView queryItems(HttpServletRequest request) throws Exception {
System.out.println(request.getParameter("id"));
//调用service查询商品列表
List<ItemsCustom> itemsList = itemsService.findItemsList(null);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("itemsList", itemsList);
// 指定逻辑视图名
modelAndView.setViewName("itemsList");
return modelAndView;
}
//方法返回 字符串,字符串就是逻辑视图名,Model作用是将数据填充到request域,在页面展示
@RequestMapping(value="/editItems",method={RequestMethod.GET})
@RequiresPermissions("item:update")//执行此方法需要"item:update"权限
public String editItems(Model model,Integer id)throws Exception{
//将id传到页面
model.addAttribute("id", id);
//调用 service查询商品信息
ItemsCustom itemsCustom = itemsService.findItemsById(id);
model.addAttribute("itemsCustom", itemsCustom);
//return "editItem_2";
return "editItem";
}
//itemsQueryVo是包装类型的pojo
//在@Validated中定义使用ValidGroup1组下边的校验
@RequestMapping("/editItemSubmit")
@RequiresPermissions("item:update")//执行此方法需要"item:update"权限
public String editItemSubmit(Model model,Integer id,
@Validated(value={ValidGroup1.class}) @ModelAttribute(value="itemsCustom") ItemsCustom itemsCustom,
BindingResult bindingResult,
//上传图片
MultipartFile pictureFile
)
1.12.5 jsp 标签授权
Jsp页面添加:
<%@ tagliburi=”http://shiro.apache.org/tags” prefix=”shiro” %>
标签名称 | 标签条件 |
---|---|
<shiro:authenticated> |
登录之后 |
<shiro:notAuthenticated> |
不在登录状态时 |
<shiro:guest> |
用户在没有RememberMe时 |
<shiro:user> |
用户在RememberMe时 |
<shiro:hasAnyRoles name="abc,123" > |
在有abc或者123角色时 |
<shiro:hasRole name="abc"> |
拥有角色abc |
<shiro:lacksRole name="abc"> |
没有角色abc |
<shiro:hasPermission name="abc"> |
拥有权限资源abc |
<shiro:lacksPermission name="abc"> |
没有abc权限资源 |
<shiro:principal> |
显示用户身份名称 |
<shiro:principal property="username"/> |
显示用户身份中的属性值 |
如果有商品修改权限页面显示“修改”链接。
<shiro:hasPermission name="item:update">
<a href="${pageContext.request.contextPath }/item/editItem.action?id=${item.id}">修改</a>
</shiro:hasPermission>
1.12.6 授权测试
当调用controller的一个方法,由于该方法加了@RequiresPermissions(“item:query”),shiro调用realm获取数据库中的权限信息,看”item:query”是否在权限数据库中,如果不存在,就拒绝访问,如果存在就授权通过。
当展示一个jsp页面时,页面中如果遇到,shiro调用realm获取数据库中的权限信息,看item:update是否在权限数据库中存在,如果不存在就拒绝访问,如果存在就授权通过。
问题
:只要遇到注解或jsp标签的授权,都会调用realm方法查询数据,需要使用缓存解决此问题。
1.13 shiro缓存
针对上边授权频繁查询数据库,需要使用shiro缓存。
1.13.1 缓存流程
shiro中提供了对认证信息和授权信息的缓存。shiro默认是关闭认证信息缓存的,对于授权信息的缓存shiro默认是开启的。主要研究授权信息缓存,因为授权的数据量大。
用户认证通过:
该用户第一次授权,调用realm查询数据库。
该用户第二次授权:不调用realm查询数据库,直接从缓存中取出授权信息(权限标识符)。
1.13.2 使用ehcache
1.13.2.1 添加Ehcache的jar包
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
1.13.2.2 配置cacheManager
在applicationContext-shiro.xml中配置缓存管理器。
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm" />
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- 缓存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
</bean>
1.13.2.3 配置shiro-ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!--diskStore:缓存数据持久化的目录 地址 -->
<diskStore path="F:\develop\ehcache" />
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
1.13.2.4 缓存清空
- 如果用户正常退出,缓存自动清空;
- 如果用户非正常退出,缓存自动清空;
- 如果修改了用户的权限,而用户不退出系统,修改的权限无法立即生效。
需要手动进行编程实现。
在权限修改后调用realm的clearCached方法
realm中定义clearCached方法:
下面的代码正常开发时 要放在service中调用。
在service中,权限修改后调用realm下边的方法。
//清除缓存
public void clearCached() {
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
super.clearCache(principals);
}
在权限修改后调用realm中的方法,realm已经由spring管理,所以从spring中获取realm实例,调用clearCached方法。
@Controller
public class ClearShiroCache {
//注入realm
@Autowired
private CustomRealm customRealm;
@RequestMapping("/clearShiroCache")
public String clearShiroCache(){
//清除缓存,将来正常开发要在service调用customRealm.clearCached()
customRealm.clearCached();
return "success";
}
}
1.14 sessionManager
和shiro整后,使用shiro的session 管理。shiro提供了sessionDao操作,sessionDao操作会话 数据。
配置sessionManager:
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm" />
<property name="sessionManager" ref="sessionManager" />
</bean>
<!-- 会话管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- session的失效时长,单位毫秒 -->
<property name="globalSessionTimeout" value="600000"/>
<!-- 删除失效的session -->
<property name="deleteInvalidSessions" value="true"/>
</bean>
1.15 验证码
1.15.1 思路
shiro使用FormAuthenticationFilter进行表单验证,验证校验的功能应该加在FormAuthenticationFilter中m,在认证之前进行验证码校验。
需要些FormAuthenticationFilter的子类,继承FormAuthenticationFilter,改写它的认证方法,在认证之前进行验证码校验。
1.15.2 自定义FormAuthenticationFilter
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
//原FormAuthenticationFilter的认证方法
@Override
protected boolean onAccessDenied(ServletRequest request,
ServletResponse response) throws Exception {
//在这里进行验证码的校验
//从session获取正确验证码
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpSession session =httpServletRequest.getSession();
//取出session的验证码(正确的验证码)
String validateCode = (String) session.getAttribute("validateCode");
//取出页面的验证码
//输入的验证和session中的验证进行对比
String randomcode = httpServletRequest.getParameter("randomcode");
if(randomcode!=null && validateCode!=null && !randomcode.equals(validateCode)){
//如果校验失败,将验证码错误失败信息,通过shiroLoginFailure设置到request中
httpServletRequest.setAttribute("shiroLoginFailure", "randomCodeError");
//拒绝访问,不再校验账号和密码
return true;
}
return super.onAccessDenied(request, response);
}
}
1.15.3 FormAuthenticationFilter配置
修改applicationContext-shiro.xml中对FormAuthenticationFilter的配置。
- 在shiroFilter中添加filters:
<!-- Shiro 的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!-- loginUrl认证提交地址,如果没有认证将会请求此地址进行认证,请求此地址将由formAuthenticationFilter进行表单认证 -->
<property name="loginUrl" value="/login.action" />
<!-- 认证成功统一跳转到first.action,建议不配置,shiro认证成功自动到上一个请求路径 -->
<property name="successUrl" value="/first.action"/>
<!-- 通过unauthorizedUrl指定没有权限操作时跳转页面-->
<property name="unauthorizedUrl" value="/refuse.jsp" />
<!-- 自定义filter配置 -->
<property name="filters">
<map>
<!-- 将自定义 的FormAuthenticationFilter注入shiroFilter中-->
<entry key="authc" value-ref="formAuthenticationFilter" />
</map>
</property>
<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
<property name="filterChainDefinitions">
<value>
<!-- 对静态资源设置匿名访问 -->
/images/** = anon
/js/** = anon
/styles/** = anon
<!-- 验证码,可匿名访问 -->
/validatecode.jsp = anon
<!-- 请求 logout.action地址,shiro去清除session-->
/logout.action = logout
<!--商品查询需要商品查询权限 ,取消url拦截配置,使用注解授权方式 -->
<!-- /items/queryItems.action = perms[item:query]
/items/editItems.action = perms[item:edit] -->
<!-- 配置记住我或认证通过可以访问的地址 -->
/index.jsp = user
/first.action = user
/welcome.jsp = user
<!-- /** = authc 所有url都必须认证通过才可以访问-->
/** = authc
<!-- /** = anon所有url都可以匿名访问 -->
</value>
</property>
</bean>
- formAuthenticationFilter定义
<!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 -->
<bean id="formAuthenticationFilter"
class="com.ty.filter.authc.MyFormAuthenticationFilter ">
<!-- 表单中账号的input名称 -->
<property name="usernameParam" value="username" />
<!-- 表单中密码的input名称 -->
<property name="passwordParam" value="password" />
</bean>
1.15.4 在登录页面添加验证码
<TR>
<TD>验证码:</TD>
<TD><input id="randomcode" name="randomcode" size="8" /> <img
id="randomcode_img" src="${baseurl}validatecode.jsp" alt=""
width="56" height="20" align='absMiddle' /> <a
href=javascript:randomcode_refresh()>刷新</a></TD>
</TR>
1.15.5 在filter配置匿名访问验证码jsp
<!-- 过虑器链定义,从上向下顺序执行,一般将/**放在最下边 -->
<property name="filterChainDefinitions">
<value>
<!-- 对静态资源设置匿名访问 -->
/images/** = anon
/js/** = anon
/styles/** = anon
<!-- 验证码,可匿名访问 -->
/validatecode.jsp = anon