项目起源:
公司随着业务的增长,各业务进行水平扩展面临拆分;随着业务的拆分各种管理系统扑面而来,为了方便权限统一管理,不得不自己开发或使用分布式权限管理(Spring Security)。Spring Security依赖Spring和初级开发人员学习难度大,中小型公司不推荐使用;Apache Shiro是一个强大易用的安全框架,Shiro的API方便理解。经过网上各路大神对shiro与spring security的比较,最终决定使用shiro开发一个独立的权限管理平台。
该项目是在张开涛跟我学shiro Demo基础上进行开发、功能完善和管理页面优化,已上传GitHub欢迎fork、start及提出改进意见。
公用模块:shiro-distributed-platform-core
自定义注解-CurrentUser
通过注解获取当前登录用户
请求拦截器-SysUserFilter
拦截所有请求
1.通过shiro Subject 获取当前用户的用户名
2.通过用户名获取用户信息
3.将用户信息保存ServletRequest对象中
代码示例:
/**
*
* @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;
}
}
Spring方法拦截器参数绑定-CurrentUserMethodArgumentResolver
通过SpringAOP拦截容器内所有Java方法参数是否有CurrentUser注解,若果有注解标识从NativeWebRequest中获取user信息进行参数绑定
代码示例:
/**
*
* @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);
}
}
认证拦截器-ServerFormAuthenticationFilter
shiro权限认证通过后进行页面跳转
代码示例:
/**
*
* @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);
}
公用接口:shiro-distributed-platform-api
权限系统核心API
AppService-应用API
AreaService-区域API
AuthorizationService-授权API
OrganizationService-组织结构API
ResourceService-资源API
RoleService-角色API
UserService-用户API
客户端:shiro-distributed-platform-client
客户端认证拦截-ClientAuthenticationFilter
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
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管理-ClientSessionDAO
客户端与服务端Session同步
客户端shiro拦截器工厂管理类-ClientShiroFilterFactoryBean
添加两个方法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]);
}
}
}
服务端:shiro-distributed-platform-server
接口暴露
通过Spring HttpInvokerServiceExporter工具将shiro-distributed-platform-api模块部分API暴露(remoteService、userService、resourceService),请参考spring-mvc-export-service.xml配置文件
配置ShiroFilterFactoryBean filterChainDefinitions属性将以上三个接口权限设置为游客、匿名(anon),请参考spring-config-shiro.xml配置文件
以上属于原创文章,转载请注明作者@怪咖
QQ:208275451
Email:yangzhao_java@163.com