zoukankan      html  css  js  c++  java
  • shiro与项目集成开发

    shiro与spring web项目开发

    加入shiro的jar包

    自定义realm

    /**
     * 自定义realm  继承授权realm 
     * @author Administrator
     *
     */
    public class CustomRealm  extends AuthorizingRealm{
    
        @Autowired
        UserMapper userMapper;
        @Autowired
        MenuMapper menuMapper;
        
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
             UsernamePasswordToken upToken = (UsernamePasswordToken) token;
                String username = upToken.getUsername();
    
                // Null username is invalid
                if (username == null) {
                    throw new AccountException("账号不能为空");
                }
                
                //模拟数据库获取数据
                User user = userMapper.findUserByUsername(username);
                //通过数据库获取username的信息
                if (user==null) {
                    return null;
                }
                SessionUser sessionUser = new SessionUser();
                sessionUser.setUser(user);
                //查询用户对应的菜单资源
                List<Menu> menu = menuMapper.getMenu(user.getId(), "menu");
                sessionUser.setMenus(menu);
                //查询用户对应的按钮资源
                List<Menu> button = menuMapper.getMenu(user.getId(), "button");        
                sessionUser.setButtons(button);
                SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(sessionUser, user.getPassword(),ByteSource.Util.bytes("tdit"), getName());
            
                return info;
        }
    
        /**
         * 实现授权的方法  principals ======认证保存的SimpleAuthenticationInfo对象
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
             if (principals == null) {
                    throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
                }
    
                SessionUser sessionUser= (SessionUser) getAvailablePrincipal(principals);
                List<String>permissions = new ArrayList<>();
                //模拟通过用户名查询数据库权限信息
                List<Menu> buttons = sessionUser.getButtons();
                for (Menu menu : buttons) {
                    permissions.add(menu.getMenuCode());
                }
    
                SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
                info.addStringPermissions(permissions);
                return info;
        }
    
    }

     添加spring-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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> 
          
         <bean id="shiroSecurityFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> 
              <!-- 配置安全管理器 -->
              <property name="securityManager" ref="securityManager"/>
              <!-- 登录的url 指的是提交登录信息的url login.do -->
              <property name="loginUrl" value="/login.do"/>
              <!-- 认证通过跳转的页码  只在浏览器第一次请求系统的时候起作用,之后就不起作用了  一般不需要配,默认跳转到你前一次请求的路径 -->
              <!-- <property name="successUrl" value="/main.do"></property> -->
              <!-- 授权没有通过跳转的页面  可以指定错误页面-->
              <property name="unauthorizedUrl" value="error.jsp"/>
              <property name="filters">
                  <map>
                      <entry key="authc" value-ref="formAuthenticationFilter"></entry>
                  </map>
              </property>
              
              <!-- shiro 连接约束配置 -->
              <property name="filterChainDefinitions">
                  <value>
                      /index.do=anon<!--登录页面可以匿名访问  -->
                      /logout.do=logout<!-- 退出过滤器 -->
                      /**=authc<!-- 需要认证才可访问  -->
                  </value>
              </property>
          </bean>
          <!-- 配置登录form 登录用户名name属性,密码name属性   存放错误信息的key值 -->
          <bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
              <property name="usernameParam" value="user"/>
              <property name="passwordParam" value="pwd"/>
              <!-- request 域中错误信息的key  用户login.do中获取认证错误信息 -->
              <property name="failureKeyAttribute" value="shiroException"/>
          </bean>
          
          <!-- 安全管理器 -->  
          <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
              <property name="realm" ref="customRealm"/>
          </bean>
          
          
          <!-- 配置自定义realm -->
          <bean id="customRealm" class="com.td.realm.CustomRealm">
              <property name="credentialsMatcher" ref="credentialsMatcher"/>
          </bean>
            
            <!-- 配置认证器 -->
         <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
             <property name="hashAlgorithmName" value="md5"/>
             <property name="hashIterations" value="2"/>
         </bean>
    </beans>

    securityManager:这个属性是必须的。

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

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

     

    shiro过滤器

    anon:例子/admins/**=anon 没有参数,表示可以匿名使用。

    authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数

    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] ,其中methodpostgetdelete等。

    port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议httphttps等,serverName是你访问的host,8081url配置里port的端口,queryString

    是你访问的url里的?后面的参数。

    authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证

    ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https

    user:例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查

    注:

    这些过滤器分为两组,一组是认证过滤器,一组是授权过滤器。

    anonauthcBasicauchcuser是认证过滤器,

    permsrolessslrestport是授权过滤器

    FormAuthenticationFilter  拦截器

    通过查看默认的拦截器链 最终发现进行认证拦截的是FormAuthenticationFilter这个拦截器

    进入它的父类AuthenticationFilter 可以看到执行的login的方法这里边发现如果登录失败,会回调FormAuthenticationFilter的一个登录失败的方法

    最终取到的就是上边默认的错误属性名 shiroLoginFailure

    自定义拦截器

    web.xml添加shiro Filter

    <!-- 配置shiro拦截器 -->
          <filter>
              <filter-name>shiroFilter</filter-name>
              <!-- 配置一个代理拦截器    因为项目启动之后 shiro的拦截器还没有加载,需要通过代理拦截器 来进行分配 -->
              <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
              <!-- 设置true由servlet容器控制filter的生命周期  false 由pring来控制生命周期-->
            <init-param>
                <param-name>targetFilterLifecycle</param-name>
                <param-value>true</param-value>
            </init-param>
            <!-- 指定拦截器的名称,如果不配置默认查找和拦截器名称(filter-name)相同的拦截器对象 -->
              <init-param>
                  <param-name>targetBeanName</param-name>
                  <!-- 配置的拦截器名称需要和spring-shiro.xml中的bean id 一样 -->
                  <param-value>shiroFilter</param-value>
              </init-param>
          </filter>
      
          <filter-mapping>
              <filter-name>shiroFilter</filter-name>
              <url-pattern>*.do</url-pattern>
          </filter-mapping>

    登录controller

    /**
         * 提交登录的方法 
         * @param request
         * @param model
         * @return
         */
        @RequestMapping("/login")
        public ModelAndView login(HttpServletRequest request,Model model) {
            // 认证失败 过滤器会把异常信息放入request域中 key 为shiroLoginFailure
            //所以在这里可以通过request获取到错误信息
            String shiroLoginFailure = (String) request.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
            if (shiroLoginFailure!=null) {
                if(UnknownAccountException.class.getName().equals(shiroLoginFailure)) {
                    model.addAttribute("error", "账号错误");
                }else if(IncorrectCredentialsException.class.getName().equals(shiroLoginFailure)) {
                    model.addAttribute("error", "密码错误");
                }else {
                    model.addAttribute("error", "认证错误");
                }
            }
            return new ModelAndView("forward:index.do");
        }
        
    
    /**
         * 登录页面
         * @return
         */
        @RequestMapping("/index")
        public String index() {
            return "login";
        }

    登录页面

    <form class="loginForm" action="${ctx}/login.do" method="post">
                    <div class="inputbox">
                        <label for="user">用户名:</label>
                        <input id="username" type="text" name="usercode" placeholder="请输入用户名" onchange="checkUser()" required/>
                         <span id="usernameError" style="color:red;"></span>${userError}
                    </div>
                    <div class="inputbox">
                        <label for="password">密码:</label>
                        <input id="password" type="password" name="pwd" placeholder="请输入密码" required/>
                        <span style="color:red;">${pwdError}</span>
                    </div>
                    <div class="subBtn">
                        <input type="submit" value="登录" />
                        <input type="reset" value="重置"/>
                    </div>
    
                </form>

    退出

    由于使用shiro的sessionManager,不用开发退出功能,使用shiro的logout拦截器即可。

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

    自定义FormAuthenticationFilter实现登录跳转到main.do页面

    经过测试发现successUrl只在浏览器第一次请求的时候才会跳转,之后再次登录不会跳转 ,查看源码发现shiro先查询session中是否有记录(上次请求),如果有获取到上次请求的记录中的url 然后跳转过去,如果没有session才会跳转到successUrl的连接中 ,所有我们只需要在跳转之前情况session即可。

    以下为自定义过滤器实现情况的操作

    public class MyFormAuthenticationFilter extends FormAuthenticationFilter{
    
        @Override
        protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
                ServletResponse response) throws Exception {
            WebUtils.getAndClearSavedRequest(request);//情况session
    //        WebUtils.redirectToSavedRequest(request,response,"/main.do");
            return super.onLoginSuccess(token, subject, request, response);
        }
    }

    现在就可以放开successUrl的连接了

    配置自定义拦截器

    使用shiro注解授权

    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>

    修改Controller代码,在方法上添加授权注解,如下:

    /**
         * 进入添加用户页面
         * @return
         */
        @RequiresPermissions("user:add")
        @RequestMapping("/userAddPage")
        public String userAddPage() {
            return "userAdd";
        }

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

    无权限refuse.jsp

    权限拦截会抛出UnauthorizedException

    spring-mvc.xml中配置

    <!-- 接收注解方式抛出的异常 unauthorizedException -->
     <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
       <property name="exceptionMappings">
          <props>
          <!-- refuse 跳转到错误页面名称 -->
             <prop key="org.apache.shiro.authz.UnauthorizedException">refuse</prop>  
          </props>
       </property> 
       </bean> 

    shiro的jsp标签

    Jsp页面添加:

    <%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>

    session管理

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

    缓存

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

    添加Ehcache的jar包

    配置

    applicationContext-shiro.xml中配置缓存管理器。

    <!-- 安全管理器 -->
        <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"/>
        </bean>
    
    <!-- 缓存管理器 -->
        <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        </bean>

    验证码

    自定义FormAuthenticationFilter

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

    public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
        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.equals(validateCode)) {
                // randomCodeError表示验证码错误 
                request.setAttribute("shiroLoginFailure", "randomCodeError");
                //拒绝访问,不再校验账号和密码 
                return true;
            }
            
            return super.onAccessDenied(request, response, mappedValue);
        }
    }

    修改FormAuthenticationFilter配置

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

    <bean id="formAuthenticationFilter"
            class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
    
    改为
            
    <bean id="formAuthenticationFilter"
            class="cn.td.tderp.shiro.MyFormAuthenticationFilter">

    登录页面

    添加验证码

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

     配置validatecode.jsp匿名访问

    修改spring-shiro.xml

    记住我

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

    配置

    <!-- 安全管理器 -->
        <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>

    修改formAuthenticationFitler添加页面中“记住我checkbox”的input名称:

    <bean id="formAuthenticationFilter"
            class="com.td.bill.shiro.MyFormAuthenticationFilter">
            <!-- 表单中账号的input名称 -->
            <property name="usernameParam" value="usercode" />
            <!-- 表单中密码的input名称 -->
            <property name="passwordParam" value="password" />
            <property name="rememberMeParam" value="rememberMe"/>
            <!-- loginurl:用户登陆地址,此地址是可以http访问的url地址 -->
            <property name="loginUrl" value="/login.do" />
        </bean>

     登陆页面

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

    <TR>
        <TD></TD>
        <TD><input type="checkbox" name="rememberMe" />自动登陆</TD>
    </TR>
  • 相关阅读:
    前端常见跨域解决方案
    前端必须要懂的浏览器缓存机制
    判断一个变量是数组还是对象
    javascript中Array常用方法
    Javascript数据类型
    Email接收验证码,以实现登录/注册/修改密码
    web安全之机器学习入门——3.2 决策树与随机森林
    web安全之机器学习入门——3.1 KNN/k近邻
    web安全之机器学习入门——2.机器学习概述
    cmd下的一些小技巧
  • 原文地址:https://www.cnblogs.com/lldsgj/p/10790855.html
Copyright © 2011-2022 走看看