zoukankan      html  css  js  c++  java
  • Spring MVC 中急速集成 Shiro 实践

       相信有很多的程序员,不愿意进行用户管理这块代码实现。

       原因之一,不同的JavaEE 系统,用户管理都会有个性化的实现,逻辑很繁琐。

       而且是系统门面,以后背锅的几率非常大,可谓是低收益高风险。

       最近在系统中集成了 Shiro,感觉这个小家伙还是相当灵活的。

       完善的用户认证和授权,干净的API,让人如沐春分。

       Apache Shiro 作为一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权,企业会话管理和加密。

       安全有时候是很复杂的,甚至是痛苦的,但它没有必要这样。框架应该尽可能掩盖复杂的地方,露出一个干净而直观的 API。

       Apache Shiro 的首要目标是易于使用和理解。

       以下是你可以用 Apache Shiro 所做的事情:

       a.验证用户来核实他们的身份

       b.对用户执行访问控制,如:

       c.判断用户是否被分配了一个确定的安全角色。

       d.判断用户是否被允许做某事。

       e.在任何环境下使用 Session API,即使没有 Web 或 EJB 容器。

       f.在身份验证,访问控制期间或在会话的生命周期,对事件作出反应。

       g.聚集一个或多个用户安全数据的数据源,并作为一个单一的复合用户“视图”。

       h.启用单点登录(SSO)功能。

       i.并发登录管理(一个账号多人登录作踢人操作)。

       j.为没有关联到登录的用户启用"Remember Me"服务。

       …

       以及更多——全部集成到紧密结合的易于使用的 API 中。

       目前主流安全框架有 SpringSecurity 和 Shiro,相比于 SpringSecurity,Shiro 轻量化,简单容易上手。

       SpringSecurity 太笨重了,难以上手,且只能在 Spring 里用,所以极力推荐Shiro。

       本文重点描述集成过程,能让你迅速的将 Shiro 集成到 JavaEE 项目中,毕竟项目都挺紧张的。

    1.前戏

        Shiro 核心jar:

           <!--权限控制 shiro-->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>${shiro.version}</version>
            </dependency>
    
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-ehcache</artifactId>
                <version>${shiro.version}</version>
            </dependency>

        JavaEE 应用开始的地方,web.xml 配置:

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

       Spring-Shiro-context 配置应用启动的使用,会帮助你做很多事情:

       <!--Shiro 关键过滤器配置-->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager"/>
            <property name="loginUrl" value="/sys/login"/> <!--请求 Url 为 get方式-->
            <property name="successUrl" value="/sys/index"/>
            <property name="filters">
                <map>
                    <entry key="authc" value-ref="formAuthenticationFilter"/>
                </map>
            </property>
            <property name="filterChainDefinitions" ref="shiroFilterChainDefinitions"/>
        </bean>
    
        <!-- Shiro 安全管理器 -->
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="systemAuthorizingRealm"/>
            <property name="cacheManager" ref="shiroCacheManager"/>
        </bean>
    
        <!--自定义系统认证域-->
        <bean id="systemAuthorizingRealm" class="com.rambo.spm.core.shiro.SysAuthorizingRealm"/>
    
        <!--shiro ehcache缓存-->
        <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
            <property name="cacheManager" ref="cacheManagerFactory"/>
        </bean>
    
        <!--扩展表单认证过滤器-->
        <bean id="formAuthenticationFilter" class="com.rambo.spm.core.shiro.FormAuthenticationFilter"/>
    
        <!--权限过滤链定义 -->
        <bean name="shiroFilterChainDefinitions" class="java.lang.String">
            <constructor-arg>
                <value>
                    /static/** = anon
                    /captcha-image = anon
                    /sys/login = authc
                    /sys/logout = logout
                    /** =user
                </value>
            </constructor-arg>
        </bean>
    
        <!--借助 SpringAOP 扫描那些使用 Shiro 注解的类-->
        <aop:config proxy-target-class="true"/>
    
        <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
            <property name="securityManager" ref="securityManager"/>
        </bean>
    
        <!--用于在实现了Initializable/Destroyable接口的 Shiro bean 初始化时回调-->
        <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    2.个性化

       请求发起的地方一般是前端,不管你是 .jsp/.php/.net.......方式都是类似

    <html>
    <body>
    <h1>login page</h1>
    <form id="" action="service/dologin" method="post">
        <label>账号:</label><input name="userName" maxLength="40"/>
        <input title="是否是管理员" type="checkbox" name="isAdmin"><label>是否为管理员</label><br>
        <label>密码:</label><input title="密码" type="password" name="password" /><br>
        <input type="submit" value="登录"/>
    </form>
    <%--用于输入后台返回的验证错误信息 --%>
    <P><c:out value="${message }"/></P>
    </body>
    </html>

       自定义项目验证域(验证域可以有多个,已可以有多种方式)。

    public class SysAuthorizingRealm extends AuthorizingRealm {
        private Log log = LogFactory.get();
    
        @Autowired
        private SysUserService sysUserService;
    
        @Autowired
        private SysRoleService sysRoleService;
    
        @Autowired
        private SysMenuService sysMenuService;
    
    
        /**
         * 获取当前用户的认证信息
         *
         * @param authcToken 携带用户认证所需的信息
         * @return 认证结果信息
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
            CaptchaUsernamePasswordToken spmToken = (CaptchaUsernamePasswordToken) authcToken;
            log.info("token:{}", ReflectionToStringBuilder.toString(spmToken));
    
            SysUser sysUser = sysUserService.getSysUserByLoginName(spmToken.getUsername());
            if (sysUser == null) {
                return null;
            }
            byte[] salt = Hex.decode(sysUser.getPasswd().substring(0, 16));
            return new SimpleAuthenticationInfo(new SpmPrincipal(sysUser), sysUser.getPasswd().substring(16), ByteSource.Util.bytes(salt), getName());
        }
    
        /**
         * 获取当前用户授权信息
         *
         * @param principals 该用户身份集合
         * @return 当前用户授权信息
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            SpmPrincipal spmPrincipal = (SpmPrincipal) super.getAvailablePrincipal(principals);
            log.info("授权当前:{}", ReflectionToStringBuilder.toString(spmPrincipal));
    
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    
            List<SysRole> sysRoleList = sysUserService.listSysRoleByUserId(spmPrincipal.getId());
            for (SysRole sysRole : sysRoleList) {
                info.addRole(sysRole.getRoleType());
    
                List<SysMenu> sysMenuList = sysRoleService.listSysMenuByRoleId(sysRole.getUuid());
                for (SysMenu sysMenu : sysMenuList) {
                    info.addStringPermission(sysMenu.getPermisson());
                }
            }
            info.addStringPermission("user");
            return info;
        }
    
        /**
         * 设定密码校验的Hash算法与迭代次数
         */
        @PostConstruct
        public void initCredentialsMatcher() {
            HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("SHA-1");
            matcher.setHashIterations(1024);
            setCredentialsMatcher(matcher);
        }
    }

        请求的后台服务,在这里你可以在进行一点业务逻辑

       /**
         * shiro 登录请求控制,真正的请求由 shiroFilter --> formAuthenticationFilter 进行处理
         * 登录成功: 跳转配置的 succssUrl,不触发该方法;
         * 登录失败: 触发该方法,可以从扩展的 formAuthenticationFilter 中获取具体的错误信息;
         */
        @PostMapping("/sys/login")
        public Object postSysLogin(HttpServletRequest httpRequest, ModelAndView modelAndView) {
            String exceptionName = (String) httpRequest.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
    
            String errorMsg = null;
            if (IncorrectCaptchaException.class.getName().equals(exceptionName)) {
                errorMsg = "验证码错误!";
            } else if (UnknownAccountException.class.getName().equals(exceptionName)) {
                errorMsg = "用户不存在!";
            } else if (IncorrectCredentialsException.class.getName().equals(exceptionName)) {
                errorMsg = "用户或密码错误!";
            } else if (exceptionName != null && StrUtil.startWith(exceptionName, "msg:")) {
                errorMsg = StrUtil.removeAll(exceptionName, "msg:");
            }
            return setModelAndView(modelAndView, "sys/sysLogin", errorMsg);
        }

         整个集成工作就结束了,是不是简单的不要不要的。

         等下次项目经理指派你做用户管理的时候,只需要花半天的时间做设计。

         借助Shiro 半天时间实现代码。

         然后将剩下的时间,做做自己喜欢的其他研究工作。

  • 相关阅读:
    周末郑州程序员朋友技术交流中的PPT
    WCF并发连接数的问题
    郑州.Net技术人员的招聘信息
    在路上
    Windows8体验(1)安装
    挖掘0day打进不同学校
    记一次绕过宝塔防火墙的BC站渗透
    一次实战中对tp5网站getshell方式的测试
    一次从弱口令到getshell
    一次HW实战
  • 原文地址:https://www.cnblogs.com/java-class/p/5475373.html
Copyright © 2011-2022 走看看