要做一款权限架构,就要适用几个流行的相关框加,struts2是我们公司首先需要考虑的,考虑到侵入性,决定通过切面的方式,在每个Action前进行权限验证,基本思路是:
1,自定义通用权限注解
2,开发抽象切面,预留传入uid的接口
3,配置struts切面,做权限拦截
以下源码是对上边功能的实现:
1,权限注解
/**
* 自定义权限注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Authority {
/**
* 权限码
* @return
*/
String authorityCode();
}
2,sturts抽象切面
public abstract class ELInterceptor implements Interceptor {
private IVerificationUser verificationUser;
private ILogicUserAreaFranchiseeService logicUserAreaFranchiseeService;
private Result result = new Result(true);
@Override
public void destroy() {
// TODO Auto-generated method stub
}
protected final Log log = LogFactory.getLog(this.getClass());
@Override
public void init() {
}
/**
* 拦截类并作权限验证
* @param invocation
* @return
* @throws Exception
*/
@Override
public String intercept(ActionInvocation invocation) throws Exception {
log.info("=intercept=>Authority Intercept");
// TODO Auto-generated method stub
String methodName = invocation.getProxy().getMethod();
Method currentMethod = invocation.getAction().getClass().getMethod(methodName);
Method[] methods = invocation.getAction().getClass().getMethods();
initAuthCode(methods);
String isTest = (String) ServletActionContext.getRequest().getParameter("authistest");
//如果该方法请求是需要进行验证的时候执行以下逻辑
if (currentMethod.isAnnotationPresent(Authority.class)) {
//取得权限验证的注解
Authority authority = currentMethod.getAnnotation(Authority.class);
log.info("=intercept=> get authorityCode");
//取得当前请求的注解的authorityCode
String authorityCode = authority.authorityCode();
/* *
* 然后可以在此判断当前用户是否拥有对应的权限,如果没有可以跳到指定的无权限提示页面,如果拥有则可以
* 继续往下执行。
**/
boolean ispass =false;
ispass = Boolean.parseBoolean(getFromVm(authorityCode));
if (ispass){
if(isTest==null ||isTest.trim().isEmpty()){
return invocation.invoke();
}else{
return "hasauth";
}
} else {
log.info("=intercept=> user not have this authorityCode");
writeJson("<html auth='NOAUTH'></html>");
return "noauth";
}
}
log.info("<=intercept=>Authority Intercept");
if(isTest==null ||isTest.trim().isEmpty()){
return invocation.invoke();
}else{
return "hasauth";
}
}
private Boolean checkOnline(AuthCheckDomain authCheckDomain){
boolean isPass = false;
AuthorityUser authorityUser =null;
PublicResult<Boolean> publicResult =verificationUser.hasAuth(authCheckDomain);
if(publicResult!=null&&publicResult.isSuccess()){
isPass = publicResult.getResult();
if(publicResult.getAuthorityUser()!=null ){
authorityUser=publicResult.getAuthorityUser();
authorityUser.setCreated(new Date());
AuthCache.authMap.put(authCheckDomain.getAuthCode().trim(),authorityUser);
}
}
return isPass;
}
private void writeJson(String json){
try {
HttpServletResponse response = ServletActionContext.getResponse();
response.setHeader("contentType", "application/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.setDateHeader("Expires", 0L);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
ServletOutputStream out = response.getOutputStream();
out.write(json.getBytes("UTF-8"));
out.flush();
}catch (Exception e){
e.printStackTrace();
}
}
private void initAuthCode(Method[] methods){
for(Method currentMethod :methods){
//如果该方法请求是需要进行验证的时候执行以下逻辑
if (currentMethod.isAnnotationPresent(Authority.class)) {
//取得权限验证的注解
Authority authority = currentMethod.getAnnotation(Authority.class);
log.info("=intercept=> get authorityCode");
//取得当前请求的注解的authorityCode
String authorityCode = authority.authorityCode();
isAuthPass(authorityCode);
}
}
toVm(result);
}
private boolean isAuthPass(String authorityCode){
log.info("=isAuthPass=>");
AuthCheckDomain authCheckDomain = new AuthCheckDomain();
Long uid=getUid();
String ip=getIP();
UserAreaFranchisee userArea = getUserArea(uid);
Long provinceId=null;
Long cityId=null;
Long townId=null;
if(userArea!=null){
provinceId=Long.valueOf(userArea.getProvinceid());
cityId=Long.valueOf(userArea.getCityid());
townId=Long.valueOf(userArea.getCountyid());
}
authCheckDomain.setAuthCode(authorityCode);
authCheckDomain.setUid(uid);
authCheckDomain.setIp(ip);
authCheckDomain.setProvinceId(provinceId);
authCheckDomain.setCityId(cityId);
authCheckDomain.setTownId(townId);
boolean ispass = false;
AuthorityUser authorityUser =null;
if(AuthCache.authMap!=null && AuthCache.authMap.containsKey(authorityCode) && AuthCache.authMap.get(authorityCode)!=null ){
authorityUser = AuthCache.authMap.get(authorityCode);
int nowLong = DateUtil.getCurrentTime();
long checkTime = nowLong-(authorityUser.getCreated().getTime()/1000);
if(checkTime> RedisIntValueUtils.MapExpireSeconds.getIntValue()){
ispass = checkOnline(authCheckDomain);
}
ispass=CheckAuth.hasAuth(authorityUser,authCheckDomain);
}else{
ispass=checkOnline(authCheckDomain);
}
toVmAuthPass(authorityCode, ispass);
return ispass;
}
private void toVmAuthPass(String authCode,boolean isPass){
result.addDefaultModel(authCode,isPass);
}
private void toVm(Result result){
ValueStack context = ActionContext.getContext().getValueStack();
Set set = result.keySet();
Iterator resultCode = set.iterator();
context.set("textProvider", this);
context.set("datePickerLocale", this.getDatePickerLocale());
String text;
while(resultCode.hasNext()) {
text = (String)resultCode.next();
context.set(text, result.get(text));
}
}
private String getFromVm(String authCode){
ValueStack context = ActionContext.getContext().getValueStack();
Map map = (Map)context.peek();
String result = ((Boolean)map.get(authCode)).toString();
return result;
}
private String getDatePickerLocale() {
String locale = this.getLocale().toString().toLowerCase();
String[] arr = locale.split("_");
if(arr[0].equals("en")) {
locale = arr[0];
} else {
locale = arr[0] + "-" + arr[1];
}
return locale;
}
public Locale getLocale() {
ActionContext ctx = ActionContext.getContext();
if(ctx != null) {
return ctx.getLocale();
} else {
return null;
}
}
private UserAreaFranchisee getUserArea(Long uid){
UserAreaFranchisee userAreaFranchisee = null;
String userJson = RedisUtils.get(RedisKeyUtils.Redis_Key_UserArea.getKeyStr()+uid,String.class);
if(userJson==null || userJson.trim().isEmpty()){
userAreaFranchisee = getUserAreaByDubbo(uid);
try {
if (userAreaFranchisee != null) {
RedisUtils.set(RedisKeyUtils.Redis_Key_UserArea.getKeyStr() + uid, JSON.toJSONString(userAreaFranchisee), RedisIntValueUtils.Redis_ExpireSeconds_UserArea.getIntValue());
}
}catch (Exception e){
log.error("=getUserArea=>set reids error",e);
}
}else{
try {
userAreaFranchisee = JSON.parseObject(userJson, UserAreaFranchisee.class);
}catch(Exception e){
log.error("=getUserArea=> json error",e);
userAreaFranchisee=null;
}
}
return userAreaFranchisee;
}
private UserAreaFranchisee getUserAreaByDubbo(Long uid){
com.el.common.result.PublicResult<UserAreaFranchisee> result =null;
try {
result = logicUserAreaFranchiseeService.getUserAreaFranchisee(uid.intValue());
}catch (Exception e){
log.error("==>error" ,e);
result=null;
}
if(result==null || !result.isSuccess() || result.getResult()==null){
return null;
}else{
return result.getResult();
}
}
private Long getProvinceId(Long uid) {
return null;
}
public Long getCityId(Long uid) {
return null;
}
public Long getTownId(Long uid) {
return null;
}
public abstract Long getUid();
public abstract String getIP();
public IVerificationUser getVerificationUser() {
return verificationUser;
}
public void setVerificationUser(IVerificationUser verificationUser) {
this.verificationUser = verificationUser;
}
public ILogicUserAreaFranchiseeService getLogicUserAreaFranchiseeService() {
return logicUserAreaFranchiseeService;
}
public void setLogicUserAreaFranchiseeService(ILogicUserAreaFranchiseeService logicUserAreaFranchiseeService) {
this.logicUserAreaFranchiseeService = logicUserAreaFranchiseeService;
}
}
此处预留了两个抽象方法,依赖具体客户端实现,分别是获取用户id和获取ip
public abstract Long getUid();public abstract String getIP();
具体切面(抽象类实现)
@Service
public class ELInterceptorImpl extends ELInterceptor {
@Autowired
private IVerificationUser verificationUser;
@Autowired
private ILogicUserAreaFranchiseeService logicUserAreaFranchiseeService;
public Long getUid(){
String suid = (String) ServletActionContext.getRequest().getSession().getAttribute("uid");
Long uid=Long.valueOf((suid==null || suid.isEmpty())?"0":suid);
return uid;
}
@Override
public String getIP() {
//这里可以从session里面取得当前的用户
String suip = (String) ServletActionContext.getRequest().getSession().getAttribute("ip");
return suip;
}
}
注意:struts切面抽象类中,两个具体的业务服务不能使用spring自动注入,因为spring是扫描包下的类,有注解的维护一个单例的存在,而抽象类不能实例化,所以会报错,我们可以在具体实现类中,注入两个属性,结果是一样的。
3,struts配置
<interceptors>
<interceptor name="authIntercept" class="com.el.authority.service.intercept.ELInterceptorImpl"/>
<interceptor-stack name="strutsDefaultStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="debugging"/>
<interceptor-ref name="scopedModelDriven"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUpload">
<param name="maximumSize">4194304</param>
<!--单个文件最大4M -->
<param name="allowedTypes">image/x-png,image/png,image/gif,image/jpeg,image/jpg,image/pjpeg,text/plain,application/octet-stream</param>
<param name="allowedExtensions">jpg,jpeg,png,gif,txt,vm</param>
</interceptor-ref>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="multiselect"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="actionMappingParams"/>
<interceptor-ref name="params">
<param name="excludeParams">dojo\..*,^struts\..*,.*\\u0023.*</param>
</interceptor-ref>
<interceptor-ref name="conversionError"/>
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="workflow">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="authIntercept"/>
</interceptor-stack>
<interceptor-stack name="taskInterceptor">
<interceptor-ref name="strutsDefaultStack"/>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="taskInterceptor"/>
<global-results>
<result name="exception">/WEB-INF/vm/error.vm</result>
<result name="error">/WEB-INF/vm/error.vm</result>
<result name="input">/WEB-INF/vm/convertError.vm</result>
<result name="noauth">/WEB-INF/vm/noauthority.vm</result>
<result name="hasauth">/WEB-INF/vm/hasauth.vm</result>
</global-results>
4,struts图示
下图是struts架构的实现,对应到刚刚的配置文件中,我们就能很容易分辨出切面的切入位置
总结:
struts切面的实现,网上有很多例子,大家不防多多参考,本文中的例子主要是给大家交代清楚整体结构,通过这些后台代码及配置,我们就能全局对用户权限控制,进行控制,但是还是有些细节,篇幅所限,大家参考后续的文章,其中会介绍ValueStack的使用,前台控件的自动显隐等。