相信有很多的程序員,不願意進行用戶管理這塊代碼實現。
原因之一,不同的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 半天時間實現代碼。
然后將剩下的時間,做做自己喜歡的其他研究工作。
