Spring MVC 中急速集成 Shiro 實踐

   相信有不少的程序員,不肯意進行用戶管理這塊代碼實現。php

   緣由之一,不一樣的JavaEE 系統,用戶管理都會有個性化的實現,邏輯很繁瑣。css

   並且是系統門面,之後背鍋的概率很是大,可謂是低收益高風險。html

   最近在系統中集成了 Shiro,感受這個小傢伙仍是至關靈活的。前端

   完善的用戶認證和受權,乾淨的API,讓人如沐春分。java

   Apache Shiro 做爲一個強大而靈活的開源安全框架,它乾淨利落地處理身份認證,受權,企業會話管理和加密。程序員

   安全有時候是很複雜的,甚至是痛苦的,但它沒有必要這樣。框架應該儘量掩蓋複雜的地方,露出一個乾淨而直觀的 API。web

   Apache Shiro 的首要目標是易於使用和理解。算法

   如下是你能夠用 Apache Shiro 所作的事情:spring

   a.驗證用戶來覈實他們的身份apache

   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 半天時間實現代碼。

     而後將剩下的時間,作作本身喜歡的其餘研究工做。

相關文章
相關標籤/搜索