shiro+spring web項目
1.在web.xml中配置filter

<!-- Apache Shiro --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
2.配置文件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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd" default-lazy-init="true"> <description>Shiro Configuration</description> <!-- 加載配置屬性文件 --> <context:property-placeholder ignore-unresolvable="true" location="classpath:jeesite.properties" /> <!-- Shiro權限過濾過濾器定義 --> <bean name="shiroFilterChainDefinitions" class="java.lang.String"> <constructor-arg> <value> /static/** = anon /userfiles/** = anon ${adminPath}/cas = cas ${adminPath}/login = authc ${adminPath}/logout = logout ${adminPath}/** = user /act/rest/service/editor/** = perms[act:model:edit] /act/rest/service/model/** = perms[act:model:edit] /act/rest/service/** = user /ReportServer/** = user </value> </constructor-arg> </bean> <!-- 安全認證過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /><!-- <property name="loginUrl" value="${cas.server.url}?service=${cas.project.url}${adminPath}/cas" /> --> <property name="loginUrl" value="${adminPath}/login" /> <property name="successUrl" value="${adminPath}?login" /> <property name="filters"> <map> <entry key="cas" value-ref="casFilter"/> <entry key="authc" value-ref="formAuthenticationFilter"/> </map> </property> <property name="filterChainDefinitions"> <ref bean="shiroFilterChainDefinitions"/> </property> </bean> <!-- CAS認證過濾器 --> <bean id="casFilter" class="org.apache.shiro.cas.CasFilter"> <property name="failureUrl" value="${adminPath}/login"/> </bean> <!-- 定義Shiro安全管理配置 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="systemAuthorizingRealm" /> <property name="sessionManager" ref="sessionManager" /> <property name="cacheManager" ref="shiroCacheManager" /> </bean> <!-- 自定義會話管理配置 --> <bean id="sessionManager" class="com.thinkgem.jeesite.common.security.shiro.session.SessionManager"> <property name="sessionDAO" ref="sessionDAO"/> <!-- 會話超時時間,單位:毫秒 --> <property name="globalSessionTimeout" value="${session.sessionTimeout}"/> <!-- 定時清理失效會話, 清理用戶直接關閉瀏覽器造成的孤立會話 --> <property name="sessionValidationInterval" value="${session.sessionTimeoutClean}"/> <!-- <property name="sessionValidationSchedulerEnabled" value="false"/> --> <property name="sessionValidationSchedulerEnabled" value="true"/> <property name="sessionIdCookie" ref="sessionIdCookie"/> <property name="sessionIdCookieEnabled" value="true"/> </bean> <!-- 指定本系統SESSIONID, 默認為: JSESSIONID 問題: 與SERVLET容器名沖突, 如JETTY, TOMCAT 等默認JSESSIONID, 當跳出SHIRO SERVLET時如ERROR-PAGE容器會為JSESSIONID重新分配值導致登錄會話丟失! --> <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg name="name" value="jeesite.session.id"/> </bean> <!-- 自定義Session存儲容器 --> <!-- <bean id="sessionDAO" class="com.thinkgem.jeesite.common.security.shiro.session.JedisSessionDAO"> --> <!-- <property name="sessionIdGenerator" ref="idGen" /> --> <!-- <property name="sessionKeyPrefix" value="${redis.keyPrefix}_session_" /> --> <!-- </bean> --> <bean id="sessionDAO" class="com.thinkgem.jeesite.common.security.shiro.session.CacheSessionDAO"> <property name="sessionIdGenerator" ref="idGen" /> <property name="activeSessionsCacheName" value="activeSessionsCache" /> <property name="cacheManager" ref="shiroCacheManager" /> </bean> <!-- 定義授權緩存管理器 --> <!-- <bean id="shiroCacheManager" class="com.thinkgem.jeesite.common.security.shiro.cache.SessionCacheManager" /> --> <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManager" ref="cacheManager"/> </bean> <!-- 保證實現了Shiro內部lifecycle函數的bean執行 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- AOP式方法級權限檢查 --> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> <property name="proxyTargetClass" value="true" /> </bean> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> </beans>
所有的登錄成功啊,權限不夠啊什么之類的,都在這個配置文件里
3.自定義realms

/** * Copyright © 2012-2014 <a href="https://github.com/thinkgem/jeesite">JeeSite</a> All rights reserved. */ package com.thinkgem.jeesite.modules.sys.security; import java.io.Serializable; import java.util.Collection; import java.util.List; import javax.annotation.PostConstruct; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.Permission; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.session.Session; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import com.thinkgem.jeesite.common.config.Global; import com.thinkgem.jeesite.common.servlet.ValidateCodeServlet; import com.thinkgem.jeesite.common.utils.Encodes; import com.thinkgem.jeesite.common.utils.SpringContextHolder; import com.thinkgem.jeesite.common.web.Servlets; import com.thinkgem.jeesite.modules.sys.entity.Menu; import com.thinkgem.jeesite.modules.sys.entity.Role; import com.thinkgem.jeesite.modules.sys.entity.User; import com.thinkgem.jeesite.modules.sys.service.SystemService; import com.thinkgem.jeesite.modules.sys.utils.LogUtils; import com.thinkgem.jeesite.modules.sys.utils.UserUtils; import com.thinkgem.jeesite.modules.sys.web.LoginController; /** * 系統安全認證實現類 * @author ThinkGem * @version 2014-7-5 */ @Service //@DependsOn({"userDao","roleDao","menuDao"}) public class SystemAuthorizingRealm extends AuthorizingRealm { private Logger logger = LoggerFactory.getLogger(getClass()); private SystemService systemService; /** * 認證回調函數, 登錄時調用 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) { UsernamePasswordToken token = (UsernamePasswordToken) authcToken; int activeSessionSize = getSystemService().getSessionDao().getActiveSessions(false).size(); if (logger.isDebugEnabled()){ logger.debug("login submit, active session size: {}, username: {}", activeSessionSize, token.getUsername()); } // 校驗登錄驗證碼 if (LoginController.isValidateCodeLogin(token.getUsername(), false, false)){ Session session = UserUtils.getSession(); String code = (String)session.getAttribute(ValidateCodeServlet.VALIDATE_CODE); if (token.getCaptcha() == null || !token.getCaptcha().toUpperCase().equals(code)){ throw new AuthenticationException("msg:驗證碼錯誤, 請重試."); } } // 校驗用戶名密碼 User user = getSystemService().getUserByLoginName(token.getUsername()); if (user != null) { if (Global.NO.equals(user.getLoginFlag())){ throw new AuthenticationException("msg:該已帳號禁止登錄."); } byte[] salt = Encodes.decodeHex(user.getPassword().substring(0,16)); return new SimpleAuthenticationInfo(new Principal(user, token.isMobileLogin()), user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName()); } else { return null; } } /** * 授權查詢回調函數, 進行鑒權但緩存中無用戶的授權信息時調用 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { Principal principal = (Principal) getAvailablePrincipal(principals); // 獲取當前已登錄的用戶 if (!Global.TRUE.equals(Global.getConfig("user.multiAccountLogin"))){ Collection<Session> sessions = getSystemService().getSessionDao().getActiveSessions(true, principal, UserUtils.getSession()); if (sessions.size() > 0){ // 如果是登錄進來的,則踢出已在線用戶 if (UserUtils.getSubject().isAuthenticated()){ for (Session session : sessions){ getSystemService().getSessionDao().delete(session); } } // 記住我進來的,並且當前用戶已登錄,則退出當前用戶提示信息。 else{ UserUtils.getSubject().logout(); throw new AuthenticationException("msg:賬號已在其它地方登錄,請重新登錄。"); } } } User user = getSystemService().getUserByLoginName(principal.getLoginName()); if (user != null) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); List<Menu> list = UserUtils.getMenuList(); for (Menu menu : list){ if (StringUtils.isNotBlank(menu.getPermission())){ // 添加基於Permission的權限信息 for (String permission : StringUtils.split(menu.getPermission(),",")){ info.addStringPermission(permission); } } } // 添加用戶權限 info.addStringPermission("user"); // 添加用戶角色信息 for (Role role : user.getRoleList()){ info.addRole(role.getEnname()); } // 更新登錄IP和時間 getSystemService().updateUserLoginInfo(user); // 記錄登錄日志 LogUtils.saveLog(Servlets.getRequest(), "系統登錄"); return info; } else { return null; } } @Override protected void checkPermission(Permission permission, AuthorizationInfo info) { authorizationValidate(permission); super.checkPermission(permission, info); } @Override protected boolean[] isPermitted(List<Permission> permissions, AuthorizationInfo info) { if (permissions != null && !permissions.isEmpty()) { for (Permission permission : permissions) { authorizationValidate(permission); } } return super.isPermitted(permissions, info); } @Override public boolean isPermitted(PrincipalCollection principals, Permission permission) { authorizationValidate(permission); return super.isPermitted(principals, permission); } @Override protected boolean isPermittedAll(Collection<Permission> permissions, AuthorizationInfo info) { if (permissions != null && !permissions.isEmpty()) { for (Permission permission : permissions) { authorizationValidate(permission); } } return super.isPermittedAll(permissions, info); } /** * 授權驗證方法 * @param permission */ private void authorizationValidate(Permission permission){ // 模塊授權預留接口 } /** * 設定密碼校驗的Hash算法與迭代次數 */ @PostConstruct public void initCredentialsMatcher() { HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(SystemService.HASH_ALGORITHM); matcher.setHashIterations(SystemService.HASH_INTERATIONS); setCredentialsMatcher(matcher); } // /** // * 清空用戶關聯權限認證,待下次使用時重新加載 // */ // public void clearCachedAuthorizationInfo(Principal principal) { // SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName()); // clearCachedAuthorizationInfo(principals); // } /** * 清空所有關聯認證 * @Deprecated 不需要清空,授權緩存保存到session中 */ @Deprecated public void clearAllCachedAuthorizationInfo() { // Cache<Object, AuthorizationInfo> cache = getAuthorizationCache(); // if (cache != null) { // for (Object key : cache.keys()) { // cache.remove(key); // } // } } /** * 獲取系統業務對象 */ public SystemService getSystemService() { if (systemService == null){ systemService = SpringContextHolder.getBean(SystemService.class); } return systemService; } /** * 授權用戶信息 */ public static class Principal implements Serializable { private static final long serialVersionUID = 1L; private String id; // 編號 private String loginName; // 登錄名 private String name; // 姓名 private boolean mobileLogin; // 是否手機登錄 // private Map<String, Object> cacheMap; public Principal(User user, boolean mobileLogin) { this.id = user.getId(); this.loginName = user.getLoginName(); this.name = user.getName(); this.mobileLogin = mobileLogin; } public String getId() { return id; } public String getLoginName() { return loginName; } public String getName() { return name; } public boolean isMobileLogin() { return mobileLogin; } // @JsonIgnore // public Map<String, Object> getCacheMap() { // if (cacheMap==null){ // cacheMap = new HashMap<String, Object>(); // } // return cacheMap; // } /** * 獲取SESSIONID */ public String getSessionid() { try{ return (String) UserUtils.getSession().getId(); }catch (Exception e) { return ""; } } @Override public String toString() { return id; } } }
4.form表單過濾器(不改也可以)

/** * Copyright © 2012-2014 <a href="https://github.com/thinkgem/jeesite">JeeSite</a> All rights reserved. */ package com.thinkgem.jeesite.modules.sys.security; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.web.util.WebUtils; import org.springframework.stereotype.Service; import com.thinkgem.jeesite.common.utils.StringUtils; /** * 表單驗證(包含驗證碼)過濾類 * @author ThinkGem * @version 2014-5-19 */ @Service public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter { public static final String DEFAULT_CAPTCHA_PARAM = "validateCode"; public static final String DEFAULT_MOBILE_PARAM = "mobileLogin"; public static final String DEFAULT_MESSAGE_PARAM = "message"; private String captchaParam = DEFAULT_CAPTCHA_PARAM; private String mobileLoginParam = DEFAULT_MOBILE_PARAM; private String messageParam = DEFAULT_MESSAGE_PARAM; @Override protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { String username = getUsername(request); String password = getPassword(request); if (password==null){ password = ""; } boolean rememberMe = isRememberMe(request); String host = StringUtils.getRemoteAddr((HttpServletRequest)request); String captcha = getCaptcha(request); boolean mobile = isMobileLogin(request); return new UsernamePasswordToken(username, password.toCharArray(), rememberMe, host, captcha, mobile); } public String getCaptchaParam() { return captchaParam; } protected String getCaptcha(ServletRequest request) { return WebUtils.getCleanParam(request, getCaptchaParam()); } public String getMobileLoginParam() { return mobileLoginParam; } protected boolean isMobileLogin(ServletRequest request) { return WebUtils.isTrue(request, getMobileLoginParam()); } public String getMessageParam() { return messageParam; } /** * 登錄成功之后跳轉URL */ public String getSuccessUrl() { return super.getSuccessUrl(); } @Override protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception { // Principal p = UserUtils.getPrincipal(); // if (p != null && !p.isMobileLogin()){ WebUtils.issueRedirect(request, response, getSuccessUrl(), null, true); // }else{ // super.issueSuccessRedirect(request, response); // } } /** * 登錄失敗調用事件 */ @Override protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { String className = e.getClass().getName(), message = ""; if (IncorrectCredentialsException.class.getName().equals(className) || UnknownAccountException.class.getName().equals(className)){ message = "用戶或密碼錯誤, 請重試."; } else if (e.getMessage() != null && StringUtils.startsWith(e.getMessage(), "msg:")){ message = StringUtils.replace(e.getMessage(), "msg:", ""); } else{ message = "系統出現點問題,請稍后再試!"; e.printStackTrace(); // 輸出到控制台 } request.setAttribute(getFailureKeyAttribute(), className); request.setAttribute(getMessageParam(), message); return true; } }
當用戶登錄的時候,會到過濾器里,它有什么作用呢,最主要的作用是:調用createToken()方法返回token,其中token中包含着用戶登錄所需要的信息,然后到realms認證方法的時候,就可以取出token,然后查庫進行操作。
不加這個過濾器,他會默認用org.apache.shiro.web.filter.authc.FormAuthenticationFilter,如果有個性需求(比如登錄的時候要加一個短信驗證碼啊什么之類的),你就需要自己實現表單過濾器。本身的org.apache.shiro.web.filter.authc.FormAuthenticationFilter,只有用戶名和密碼之類常用的。
5.Controller類,shiro權限
@RequiresPermissions("sys:menu:view")
@RequestMapping("list"})
public String list(Model model) {
return "/list";
}
url對應的權限配置
6.jsp頁面
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"/> 顯示用戶身份中的屬性值
7.配置文件解釋:
1.自定義過濾器

<!-- Shiro權限過濾過濾器定義 --> <bean name="shiroFilterChainDefinitions" class="java.lang.String"> <constructor-arg> <value> /static/** = anon /userfiles/** = anon ${adminPath}/cas = cas ${adminPath}/login = authc ${adminPath}/logout = logout ${adminPath}/** = user /act/rest/service/editor/** = perms[act:model:edit] /act/rest/service/model/** = perms[act:model:edit] /act/rest/service/** = user /ReportServer/** = user </value> </constructor-arg> </bean>

過濾器簡稱 對應的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是表單認證,沒有參數 perms:例子/admins/user/**=perms[user:add:*],參數可以寫多個,多個時必須加上引號,並且參數之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個參數時必須每個參數都通過才通過,想當於isPermitedAll()方法。 user:例如/admins/user/**=user沒有參數表示必須存在用戶, 身份認證通過或通過記住我認證通過的可以訪問,當登入操作時不做檢查 1、在applicationContext-shiro.xml中配置filter規則 <!--商品查詢需要商品查詢權限 --> /items/queryItems.action = perms[item:query] 2、用戶在認證通過后,請求/items/queryItems.action 3、被PermissionsAuthorizationFilter攔截,發現需要“item:query”權限 4、PermissionsAuthorizationFilter調用realm中的doGetAuthorizationInfo獲取數據庫中正確的權限 5、PermissionsAuthorizationFilter對item:query 和從realm中獲取權限進行對比,如果“item:query”在realm返回的權限列表中,授權通過。
2.shiro安全過濾器

<!-- 安全認證過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /><!-- <property name="loginUrl" value="${cas.server.url}?service=${cas.project.url}${adminPath}/cas" /> --> <property name="loginUrl" value="${adminPath}/login" /> <property name="successUrl" value="${adminPath}?login" /> <property name="filters"> <map> <entry key="cas" value-ref="casFilter"/> <entry key="authc" value-ref="formAuthenticationFilter"/> </map> </property> <property name="filterChainDefinitions"> <ref bean="shiroFilterChainDefinitions"/> </property> </bean>
loginUrl:登錄地址
successUrl:成功登錄地址
filterChainDefinitions:自定義的過濾器添加到這兒,等等的參數
流程:
1.通過瀏覽器訪問路徑,配置文件查看,是否需要認證等,如果不需要,直接訪問controller
2.如果需要認證,通過配置文件的loginUrl,跳到這個地址,輸入用戶名、密碼等
3.登錄:1.訪問自定義的form表單過濾器FormAuthenticationFilter(自己起的名字和shiro一樣了,所以。。)的createToken方法,裝配token;
如果沒有自定義表單過濾器,默認的FormAuthenticationFilter會自動裝配表單token
2.訪問自定義realms的認證方法doGetAuthenticationInfo(),查庫(或者緩存),判斷用戶名和密碼是否正確。
4.如果登錄之后訪問的url,通過配置文件里的配置需要權限:
調用自定義realms的授權方法:doGetAuthorizationInfo(),查庫(或者緩存),查出用戶權限,判斷是否擁有權限,沒權訪問,跳到響應的refuse配置的路徑,有權訪問,跳到響應的url