[TOC]java
更新記錄git
客戶端集成示例 2017-12-28github
公司隨着業務的增加,各業務進行水平擴展面臨拆分;隨着業務的拆分各類管理系統撲面而來,爲了方便權限統一管理,不得不本身開發或使用分佈式權限管理(Spring Security)。Spring Security依賴Spring和初級開發人員學習難度大,中小型公司不推薦使用;Apache Shiro是一個強大易用的安全框架,Shiro的API方便理解。通過網上各路大神對shiro與spring security的比較,最終決定使用shiro開發一個獨立的權限管理平臺。web
該項目是在張開濤跟我學shiro Demo基礎上進行開發、功能完善和管理頁面優化,已上傳GitHub歡迎fork、start及提出改進意見。算法
經過註解獲取當前登陸用戶spring
攔截全部請求 1.經過shiro Subject 獲取當前用戶的用戶名 2.經過用戶名獲取用戶信息 3.將用戶信息保存ServletRequest對象中 代碼示例:apache
/**
*
* @ClassName: SysUserFilter
* @Description: 請求攔截器
* @author yangzhao
* @date 2017年12月20日 下午2:10:23
*
*/
public class SysUserFilter extends PathMatchingFilter {
@Autowired
private UserService userService;
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
String username = (String)SecurityUtils.getSubject().getPrincipal();
User user = userService.findByUsername(username);
request.setAttribute(Constant.CURRENT_USER,user);
return true;
}
}
複製代碼
經過SpringAOP攔截容器內全部Java方法參數是否有CurrentUser註解,若果有註解標識從NativeWebRequest中獲取user信息進行參數綁定 代碼示例:api
/**
*
* @ClassName: CurrentUserMethodArgumentResolver
* @Description: Spring方法攔截器參數綁定
* @author yangzhao
* @date 2017年12月20日 下午2:07:55
*
*/
public class CurrentUserMethodArgumentResolver implements HandlerMethodArgumentResolver {
public CurrentUserMethodArgumentResolver() {
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(CurrentUser.class)) {
return true;
}
return false;
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
CurrentUser currentUserAnnotation = parameter.getParameterAnnotation(CurrentUser.class);
return webRequest.getAttribute(currentUserAnnotation.value(), NativeWebRequest.SCOPE_REQUEST);
}
}
複製代碼
shiro權限認證經過後進行頁面跳轉 代碼示例:spring-mvc
/**
*
* @ClassName: ServerFormAuthenticationFilter
* @Description: 認證攔截器-頁面跳轉
* @author yangzhao
* @date 2017年12月20日 下午2:10:18
*
*/
public class ServerFormAuthenticationFilter extends FormAuthenticationFilter {
@Override
protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
String fallbackUrl = (String) getSubject(request, response)
.getSession().getAttribute("authc.fallbackUrl");
if(StringUtils.isEmpty(fallbackUrl)) {
fallbackUrl = getSuccessUrl();
}
WebUtils.redirectToSavedRequest(request, response, fallbackUrl);
}
複製代碼
1.判斷是否定證經過 若未認證進入下一步 2.從ServletRequest獲取回調url 3.獲取默認回調url(客戶端IP和端口) 4.將默認回調url保存到session中 5.將第2步中的回調url保存到ClientSavedRequest中(方便server回調時返回到當前請求url) 5.當前請求重定向到server端登陸頁面 代碼示例:安全
/**
*
* @ClassName: AppService
* @Description: 客戶端認證攔截
* @author yangzhao
* @date 2017年12月20日 下午2:03:43
*
*/
public class ClientAuthenticationFilter extends AuthenticationFilter {
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = getSubject(request, response);
return subject.isAuthenticated();
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
String backUrl = request.getParameter("backUrl");
saveRequest(request, backUrl, getDefaultBackUrl(WebUtils.toHttp(request)));
redirectToLogin(request, response);
return false;
}
protected void saveRequest(ServletRequest request, String backUrl, String fallbackUrl) {
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
HttpServletRequest httpRequest = WebUtils.toHttp(request);
session.setAttribute("authc.fallbackUrl", fallbackUrl);
SavedRequest savedRequest = new ClientSavedRequest(httpRequest, backUrl);
session.setAttribute(WebUtils.SAVED_REQUEST_KEY, savedRequest);
}
private String getDefaultBackUrl(HttpServletRequest request) {
String scheme = request.getScheme();
String domain = request.getServerName();
int port = request.getServerPort();
String contextPath = request.getContextPath();
StringBuilder backUrl = new StringBuilder(scheme);
backUrl.append("://");
backUrl.append(domain);
if("http".equalsIgnoreCase(scheme) && port != 80) {
backUrl.append(":").append(String.valueOf(port));
} else if("https".equalsIgnoreCase(scheme) && port != 443) {
backUrl.append(":").append(String.valueOf(port));
}
backUrl.append(contextPath);
backUrl.append(getSuccessUrl());
return backUrl.toString();
}
}
複製代碼
ClientRealm繼承自Shiro AuthorizingRealm 該類忽略doGetAuthenticationInfo方法實現,全部認證操做會轉到Server端實現 代碼示例:
/**
*
* @ClassName: ClientRealm
* @Description: 客戶端shiro Realm
* @author yangzhao
* @date 2017年12月20日 下午2:03:43
*
*/
public class ClientRealm extends AuthorizingRealm {
private RemoteService remoteService;
private String appKey;
public void setRemoteService(RemoteService remoteService) {
this.remoteService = remoteService;
}
public void setAppKey(String appKey) {
this.appKey = appKey;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
PermissionContext context = remoteService.getPermissions(appKey, username);
authorizationInfo.setRoles(context.getRoles());
authorizationInfo.setStringPermissions(context.getPermissions());
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//永遠不會被調用
throw new UnsupportedOperationException("永遠不會被調用");
}
}
複製代碼
實時更新、獲取遠程session
添加兩個方法setFiltersStr、setFilterChainDefinitionsStr,方便在properties文件中配置攔截器和定義過濾鏈 代碼示例:
/**
*
* @ClassName: ClientShiroFilterFactoryBean
* @Description: 添加兩個方法setFiltersStr、setFilterChainDefinitionsStr,方便在properties文件中配置攔截器和定義過濾鏈
* @author yangzhao
* @date 2017年12月20日 下午2:03:43
*
*/
public class ClientShiroFilterFactoryBean extends ShiroFilterFactoryBean implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void setFiltersStr(String filters) {
if(StringUtils.isEmpty(filters)) {
return;
}
String[] filterArray = filters.split(";");
for(String filter : filterArray) {
String[] o = filter.split("=");
getFilters().put(o[0], (Filter)applicationContext.getBean(o[1]));
}
}
public void setFilterChainDefinitionsStr(String filterChainDefinitions) {
if(StringUtils.isEmpty(filterChainDefinitions)) {
return;
}
String[] chainDefinitionsArray = filterChainDefinitions.split(";");
for(String filter : chainDefinitionsArray) {
String[] o = filter.split("=");
getFilterChainDefinitionMap().put(o[0], o[1]);
}
}
}
複製代碼
經過Spring HttpInvokerServiceExporter工具將shiro-distributed-platform-api模塊部分API暴露(remoteService、userService、resourceService),請參考spring-mvc-export-service.xml配置文件
配置ShiroFilterFactoryBean filterChainDefinitions屬性將以上三個接口權限設置爲遊客、匿名(anon),請參考spring-config-shiro.xml配置文件
第一步: 在項目resources目錄下新建shiro-client.properties配置文件
#各應用的appKey
client.app.key=1f38e90b-7c56-4c1d-b3a5-7b4b6ec94778
#遠程服務URL地址
client.remote.service.url=http://127.0.0.1:8080 #根據實際應用地址配置
#登陸地址
client.login.url=http://127.0.0.1:8080/login #根據實際應用地址配置
#登陸成功後,默認重定向到的地址
client.success.url=/
#未受權重定向到的地址
client.unauthorized.url=http://127.0.0.1:8080/login #根據實際應用地址配置
#session id 域名
client.cookie.domain=
#session id 路徑
client.cookie.path=/
#cookie中的session id名稱
client.session.id=sid
#cookie中的remember me名稱
client.rememberMe.id=rememberMe
複製代碼
第二步: 在項目resources目錄下新建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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="remoteRealm" class="com.yz.shiro.client.ClientRealm">
<property name="appKey" value="${client.app.key}"/>
<property name="remoteService" ref="remoteService"/>
</bean>
<!-- 會話ID生成器 -->
<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
<!-- 會話Cookie模板 -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="${client.session.id}"/>
<property name="httpOnly" value="true"/>
<property name="maxAge" value="-1"/>
<property name="domain" value="${client.cookie.domain}"/>
<property name="path" value="${client.cookie.path}"/>
</bean>
<!-- rememberMe管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<!-- rememberMe cookie加密的密鑰 建議每一個項目都不同 默認AES算法 密鑰長度(128 256 512 位)-->
<property name="cipherKey"
value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>
<property name="cookie" ref="rememberMeCookie"/>
</bean>
<!-- 會話DAO -->
<bean id="sessionDAO" class="com.yz.shiro.client.ClientSessionDAO">
<property name="sessionIdGenerator" ref="sessionIdGenerator"/>
<property name="appKey" value="${client.app.key}"/>
<property name="remoteService" ref="remoteService"/>
</bean>
<!-- 會話管理器 -->
<bean id="sessionManager" class="com.yz.shiro.client.ClientWebSessionManager">
<property name="deleteInvalidSessions" value="false"/>
<property name="sessionValidationSchedulerEnabled" value="false"/>
<property name="sessionDAO" ref="sessionDAO"/>
<property name="sessionIdCookieEnabled" value="true"/>
<property name="sessionIdCookie" ref="sessionIdCookie"/>
</bean>
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="${client.rememberMe.id}"/>
<property name="httpOnly" value="true"/>
<property name="maxAge" value="2592000"/><!-- 30天 -->
<property name="domain" value="${client.cookie.domain}"/>
<property name="path" value="${client.cookie.path}"/>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="remoteRealm"/>
<property name="sessionManager" ref="sessionManager"/>
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
<!-- 至關於調用SecurityUtils.setSecurityManager(securityManager) -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean>
<bean id="clientAuthenticationFilter" class="com.yz.shiro.client.ClientAuthenticationFilter"/>
<bean id="sysUserFilter" class="com.yz.shiro.core.filter.SysUserFilter"/>
<!-- Shiro的Web過濾器 -->
<bean id="shiroFilter" class="com.yz.shiro.client.ClientShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="${client.login.url}"/>
<property name="successUrl" value="${client.success.url}"/>
<property name="unauthorizedUrl" value="${client.unauthorized.url}"/>
<property name="filters">
<util:map>
<entry key="authc" value-ref="clientAuthenticationFilter"/>
<entry key="sysUser" value-ref="sysUserFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/web/** = anon
/**= authc,sysUser
</value>
</property>
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<import resource="classpath*:spring-client-remote-service.xml"/>
</beans>
複製代碼
第三步 將新增的兩個配置文件引入spring-context配置文件中
<?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">
<context:property-placeholder location="classpath:shiro-client.properties" ignore-unresolvable="true"/>
<import resource="spring-shiro.xml"/>
</beans>
複製代碼
以上屬於原創文章,轉載請註明做者@怪咖 QQ:208275451 Email:yangzhao_java@163.com