shiro与spring web项目整合

shiro与spring web项目整合

本文章只对shiro 部分进行讲解

shiro与springweb项目整合在“基于url拦截实现的工程”基础上整合,基于url拦截实现的工程的技术架构是springmvc+mybatis,整合注意两点:

         1、shiro与spring整合

         2、加入shiro对web应用的支持


1.0 加入shiro的jar包



1.1 web.xml添加shiro Filter

<!-- shiro过虑器,DelegatingFilterProxy通过代理模式将spring容器中的beanfilter关联起来 -->
    <filter>
       <filter-name>shiroFilter</filter-name>
       <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
       <!-- 设置trueservlet容器控制filter的生命周期 -->
       <init-param>
           <param-name>targetFilterLifecycle</param-name>
           <param-value>true</param-value>
       </init-param>
       <!-- 设置spring容器filterbean 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.2 创建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" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/beans    
                        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd    
                        http://www.springframework.org/schema/context    
                        http://www.springframework.org/schema/context/spring-context-3.1.xsd    
                        http://www.springframework.org/schema/mvc    
                        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<!-- 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" />
       <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.itcast.ssm.realm.CustomRealm1">
    </bean>
</beans>

securityManager:这个属性是必须的。

loginUrl:没有登录认证的用户请求将跳转到此地址进行认证,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。

unauthorizedUrl:没有权限默认跳转的页面。


1.2 自定义Realm 


此realm先不从数据库查询权限数据,当前需要先将shiro整合完成,在上边章节定义的realm基础上修改。

public class CustomRealm1 extends AuthorizingRealm {
 
    @Override
    public String getName() {
       return "customRealm";
    }
 
    // 支持什么类型的token
    @Override
    public boolean supports(AuthenticationToken token) {
       return token instanceof UsernamePasswordToken;
    }
 
    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
           AuthenticationToken token) throws AuthenticationException{
 
       // token获取用户身份信息
       String username = (String) token.getPrincipal();
       // username从数据库中查询
       // ....
       // 如果查询不到则返回null
       if (!username.equals("zhang")) {// 这里模拟查询不到
           return null;
       }
 
       // 获取从数据库查询出来的用户密码
       String password = "123";// 这里使用静态数据模拟。。
       
       // 根据用户id从数据库取出菜单
       //...先用静态数据
       List<SysPermission> menus = newArrayList<SysPermission>();;
       
       SysPermission sysPermission_1 = new SysPermission();
       sysPermission_1.setName("商品管理");
       sysPermission_1.setUrl("/item/queryItem.action");
       SysPermission sysPermission_2 = new SysPermission();
       sysPermission_2.setName("用户管理");
       sysPermission_2.setUrl("/user/query.action");
       
       menus.add(sysPermission_1);
       menus.add(sysPermission_2);
       
       // 构建用户身份信息
       ActiveUser activeUser = new ActiveUser();
       activeUser.setUserid(username);
       activeUser.setUsername(username);
       activeUser.setUsercode(username);
       activeUser.setMenus(menus);
 
       // 返回认证信息由父类AuthenticatingRealm进行认证
       SimpleAuthenticationInfo simpleAuthenticationInfo = newSimpleAuthenticationInfo(
              activeUser, password, getName());
 
       return simpleAuthenticationInfo;
    }
 
    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
           PrincipalCollection principals) {
       // 获取身份信息
       ActiveUser activeUser = (ActiveUser)principals.getPrimaryPrincipal();
       //用户id
       String userid = activeUser.getUserid();
       // 根据用户id从数据库中查询权限数据
       // ....这里使用静态数据模拟
       List<String> permissions = newArrayList<String>();
       permissions.add("item:query");
        permissions.add("item:update");
 
       // 将权限信息封闭为AuthorizationInfo
 
       SimpleAuthorizationInfo simpleAuthorizationInfo = newSimpleAuthorizationInfo();
       for (String permission : permissions) {
           simpleAuthorizationInfo.addStringPermission(permission);
       }
 
       return simpleAuthorizationInfo;
    }
 
}

1.3 登陆controller代码

// 用户登陆提交
    @RequestMapping("/login")
    public Stringloginsubmit(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.4 跳转首页的controller 代码

由于session由shiro管理,需要修改首页的controller方法,将session中的数据通过model传到页面。

//系统首页
    @RequestMapping("/first")
    public String first(Model model)throws Exception{
       
       //主体
       Subject subject = SecurityUtils.getSubject();
       //身份
       ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
       model.addAttribute("activeUser", activeUser);
       return "/first";
    }

1.5 退出

由于使用shirosessionManager,不用开发退出功能,使用shirologout拦截器即可

<!-- 退出拦截,请求logout.action执行退出操作 -->

/logout.action = logout


1.6 无权限refuse.jsp

当用户无操作权限,shiro将跳转到refuse.jsp页面。


1.7 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是授权过滤器


2.0 认证

添加凭证匹配器实现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>

2.1 修改Realm 认证方法

修改realm代码从数据库中查询用户身份信息,将sysService注入realm。

public class CustomRealm1 extends AuthorizingRealm {
 
    @Autowired
    private SysService sysService;
 
    @Override
    public String getName() {
       return "customRealm";
    }
 
    // 支持什么类型的token
    @Override
    public boolean supports(AuthenticationToken token) {
       return token instanceof UsernamePasswordToken;
    }
 
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
           AuthenticationToken token) throws AuthenticationException{
       // token中获取用户身份
       String usercode = (String) token.getPrincipal();
 
       SysUser sysUser = null;
       try {
           sysUser = sysService.findSysuserByUsercode(usercode);
       } catch (Exception e) {
           // TODO Auto-generatedcatch block
           e.printStackTrace();
       }
 
       // 如果账号不存在
       if (sysUser == null) {
           return null;
       }
 
       // 根据用户id取出菜单
       List<SysPermission> menus = null;
       try {
           menus = sysService.findMenuList(sysUser.getId());
       } catch (Exception e) {
           // TODO Auto-generatedcatch block
           e.printStackTrace();
       }
       // 用户密码
       String password = sysUser.getPassword();
       //       String salt = sysUser.getSalt();
       
       // 构建用户身体份信息
       ActiveUser activeUser = new ActiveUser();
       activeUser.setUserid(sysUser.getId());
       activeUser.setUsername(sysUser.getUsername());
       activeUser.setUsercode(sysUser.getUsercode());
       activeUser.setMenus(menus);
       
       SimpleAuthenticationInfo simpleAuthenticationInfo = newSimpleAuthenticationInfo(
              activeUser, password, ByteSource.Util.bytes(salt),getName());
       
       return simpleAuthenticationInfo;
    }
 
    .....
 
}

3.0 授权

修改realm代码从数据库中查询权限信息,将sysService注入realm

public class CustomRealm1 extends AuthorizingRealm {
 
    @Autowired
    private SysService sysService;
 
    @Override
    public String getName() {
       return "customRealm";
    }
 
    // 支持什么类型的token
    @Override
    public boolean supports(AuthenticationToken token) {
       return token instanceof UsernamePasswordToken;
    }
 
 
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
           PrincipalCollection principals) {
       
       //身份信息
       ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
       //用户id
       String userid = activeUser.getUserid();
       //获取用户权限
       List<SysPermission> permissions = null;
       try {
           permissions = sysService.findSysPermissionList(userid);
       } catch (Exception e) {
           // TODO Auto-generatedcatch block
           e.printStackTrace();
       }
       //构建shiro授权信息
       SimpleAuthorizationInfo simpleAuthorizationInfo = newSimpleAuthorizationInfo();
       for(SysPermission sysPermission:permissions){
           simpleAuthorizationInfo.addStringPermission(sysPermission.getPercode());
        }
       
       return simpleAuthorizationInfo;
       
    }
 
}

3.1 对controller开启AOP

在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>

3.2 权限注解控制

商品查询controller方法添加权限(item:query):

// 查询商品列表

    @RequestMapping("/queryItem")

    @RequiresPermissions("item:query")

    public ModelAndView queryItem()throws Exception {

上边代码@RequiresPermissions("item:query")表示必须拥有“item:query”权限方可执行。


3.3 标签的介绍

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"/>     显示用户身份中的属性值

 

3.4 jsp添加权限标签

<shiro:hasPermission name="item:update">  xxxx    </shiro:hasPermission>

4.0 shiro的缓存

shiro每次授权都会通过realm获取权限信息,为了提高访问速度需要添加缓存,第一次从realm中读取权限数据,之后不再读取,这里Shiro和Ehcache整合。


4.1 加入Ehcache 的jar包


4.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>

4.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>

4.4 清除缓存

当用户权限修改后,用户再次登陆shiro会自动调用realm从数据库获取权限数据,如果在修改权限后想立即清除缓存则可以调用realm的clearCache方法清除缓存。

 

   realm中定义clearCached方法:

//清除缓存
    public void clearCached(){
        PrincipalCollectionprincipals = SecurityUtils.getSubject().getPrincipals();
       super.clearCache(principals);
    }
在权限修改后调用realm中的方法,realm已经由spring管理,所以从spring中获取realm实例,调用clearCached方法

5.0 session管理

在applicationContext-shiro.xml中配置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>

6.0 验证码

6.1 自定义 FormAuthenticationFilter

需要在验证账号和名称之前校验验证码。

public class MyFormAuthenticationFilter extendsFormAuthenticationFilter {
    protected boolean onAccessDenied(ServletRequest request,
           ServletResponse response, Object mappedValue) throws Exception {
 
       // 校验验证码
       // session获取正确的验证码
       HttpSession session =((HttpServletRequest)request).getSession();
       //页面输入的验证码
       String randomcode = request.getParameter("randomcode");
       //session中取出验证码
       String validateCode = (String)session.getAttribute("validateCode");
       if (randomcode!=null && validateCode!=null) {
           if(!randomcode.equals(validateCode)) {
               // randomCodeError表示验证码错误
               request.setAttribute("shiroLoginFailure", "randomCodeError");
               //拒绝访问,不再校验账号和密码
               return true; 
           }
       }
       return super.onAccessDenied(request, response, mappedValue);
    }
}

6.2 FormAuthenticationFilter配置

修改applicationContext-shiro.xml中对FormAuthenticationFilter的配置

  在shiroFilter中添加filters:

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
       <property name="filters">
           <map>
              <!--FormAuthenticationFilter是基于表单认证的过虑器 -->
              <entry key="authc"value-ref="formAuthenticationFilter" />
           </map>
       </property>
.....
.....

6.3 formAuthenticationFilter 定义

<!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 -->
    <bean id="formAuthenticationFilter"
    class="org.apache.shiro.web.filter.authc.MyFormAuthenticationFilter">
       <!-- 表单中账号的input名称 -->
       <property name="usernameParam"value="username" />
       <!-- 表单中密码的input名称 -->
       <property name="passwordParam"value="password" />
 </bean>

6.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>

6.5 配置validatecode.jsp匿名访问

修改applicationContext-shiro.xml:




7.0 记住我

用户登陆选择“自动登陆”本次登陆成功会向cookie写身份信息,下次登陆从cookie中取出身份信息实现自动登陆。


7.1 用户身份实现java.io.Serializable接口






7.2 配置rememberMeManager


<!-- 安全管理器 -->
    <bean id="securityManager"class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
       <property name="realm"ref="userRealm" />
       <property name="sessionManager"ref="sessionManager" />
       <property name="cacheManager"ref="cacheManager"/>
       <!-- 记住我 -->
       <property name="rememberMeManager"ref="rememberMeManager"/>
    </bean>
 
<!--rememberMeManager管理器 -->
    <bean id="rememberMeManager"class="org.apache.shiro.web.mgt.CookieRememberMeManager">
       <property name="cookie"ref="rememberMeCookie" />
    </bean>
    <!-- 记住我cookie -->
    <bean id="rememberMeCookie"class="org.apache.shiro.web.servlet.SimpleCookie">
       <constructor-arg value="rememberMe"/>
       <!-- 记住我cookie生效时间30 -->
       <property name="maxAge"value="2592000" />
    </bean>

7.3 FormAuthenticationFilter配置

修改formAuthenticationFitler添加页面中“记住我checkbox”的input名称
<bean id="formAuthenticationFilter"
       class="cn.itcast.ssm.shiro.MyFormAuthenticationFilter">
       <!-- 表单中账号的input名称 -->
       <property name="usernameParam"value="usercode" />
       <!-- 表单中密码的input名称 -->
       <property name="passwordParam"value="password" />
       <property name="rememberMeParam"value="rememberMe"/>
    </bean>

7.4 登录页面添加记住我按钮

在login.jsp中添加“记住我”checkbox。

<TR>
                         <TD></TD>
                         <TD>
                         <input type="checkbox"name="rememberMe" />自动登陆
                         </TD>
                     </TR>
提醒:账号zhangsan 密码:111111
SpringMVC+shiro+Spring+Mybatis+maven 项目源码下载: 点击打开链接
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值