api接口的实现

来源:互联网 发布:监管网络的部门 编辑:程序博客网 时间:2024/05/06 16:45

我们目前有一个系统,使用spring data jpa访问数据库,使用spring mvc提供rest接口给网站系统,同时使用shiro提供权限控制,目前需要对外部系统提供接口,需要满足以下情况:
* 若目前已经存在了这样的接口, 不再另外提供,同时权限部分得满足;
* 提供外部系统的接口权限使用token实现,即外部系统需要先获取到token,然后将token设置到cookie上,系统再根据token获取用户信息判断权限,token在一定时间内有效。
* 提供给外部系统的不能访问原系统的接口(不共用的接口);

实现思路

首先,每一个@RequestMapping可以配置多个不同的请求路径,对于外部接口提供的接口,添加API后缀即可区分,添加拦截器实现对后缀是API接口的拦截(获取token,并自动登陆系统),在完成请求时通过封装将结果同一格式返回到前台,用户登录时,将token和用户对于的user信息保存到缓存中。

实现

  • 多个用户登录踢出及session过期处理 拦截器
/** * @类名 KickoutSessionControlFilter * @描述   多个用户登录踢出及session过期处理 拦截器 * @作者 zhuxl * @创建日期 2016年12月30日 下午2:47:33 */public class KickoutSessionControlFilter implements Filter {    private Logger logger=Logger.getLogger(KickoutSessionControlFilter.class);    private UserSessionRedisService userSessionRedisService;    private UserService userService;    public void destroy() {    }    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)            throws IOException, ServletException {        HttpServletRequest request=(HttpServletRequest)req;        String url=request.getRequestURI();        if(url.matches("^.{0,}/api/\\w{1,}/\\w{1,}API.{0,}$")){//是外部接口访问,从cookie中获取token并登陆            Cookie[] cookies=request.getCookies();            if(cookies!=null&&cookies.length>0){                for(Cookie cookie:cookies){                    if("token".equalsIgnoreCase(cookie.getName())){                        String token=cookie.getValue();                        String userId=userSessionRedisService.get(SessionType.API, token, -1);                        User user=userService.findById(userId);                        if(user!=null){                            Subject curUser=SecurityUtils.getSubject();                            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(                                      user.getId(), user.getPassword());                            try{                                 curUser.login(usernamePasswordToken);                            }catch(Exception e){                                e.printStackTrace();                            }                            request.getSession(true).setAttribute("user", user);                        }                        filterChain.doFilter(req, res);                        return;                    }                }            }            filterChain.doFilter(req, res);            return;        }        //web、微信登录 用户未登录,通过,后面使用shiro进行权限(角色)拦截        Subject subject = SecurityUtils.getSubject();        HttpServletResponse response = (HttpServletResponse) res;        if (!subject.isAuthenticated() && !subject.isRemembered()) {            filterChain.doFilter(req, res);            return;        }        Session session = subject.getSession(false);        if(session==null){            filterChain.doFilter(req, res);            return;        }        User user=(User)session.getAttribute("user");        SessionType type=(SessionType)session.getAttribute("loginType");        if(user==null||type==null){            filterChain.doFilter(req, res);            return;        }        //获取用户session        String sessionId=null;        if(type==SessionType.WECHAT){            sessionId=userSessionRedisService.get(type,user.getOpenId(), -1);        }else if(type==SessionType.WEB){            sessionId=userSessionRedisService.get(type,user.getId(),1800000);        }        if(StrUtil.isBlank(sessionId)){            //用户session已经失效            logger.info("登录失效了!");            try{                subject.logout();            }catch(Exception ex){            }            response.setStatus(510);            Map<String, Object> maps=new HashMap<String, Object>();            maps.put("code", ErrorCode.NoLogin);            maps.put("msg", "用户登录已失效!!");            response.getOutputStream().write(ReturnFormat.retParam(maps).getBytes());            return;        }else if(session.getId().equals(sessionId)){            //用户已经登录,并且是同一个session,继续            logger.info("用户已登录系统!!");            filterChain.doFilter(req, res);            return;        }else if(type==SessionType.WECHAT){            //用户已经在其他地方登录了,对于微信,直接挤上去            try{                subject.logout();            }catch(Exception ex){                ex.printStackTrace();            }            logger.info("用户已经在其他地方登录了,登录类型为微信,将直接挤上去");            User newUser=userService.findByOpenId(user.getOpenId());            if(newUser!=null){//存在该openId                Subject curUser=SecurityUtils.getSubject();                Session newSession=curUser.getSession(true);                newSession.setAttribute("user", newUser);                newSession.setAttribute("loginType", SessionType.WECHAT);                newSession.setTimeout(1000*60*60*24);                userSessionRedisService.add(type,newUser.getOpenId(), newSession.getId().toString(),-1);                UsernamePasswordToken token = new UsernamePasswordToken(                          newUser.getOpenId(), newUser.getPassword());                try{                     curUser.login(token);                }catch(Exception e){                    e.printStackTrace();                }                filterChain.doFilter(req, res);            }else{//不存在openID                response.setStatus(510);                Map<String, Object> maps=new HashMap<String, Object>();                maps.put("code", ErrorCode.NoLogin);                maps.put("msg", "用户未绑定账号!!");                response.getOutputStream().write(ReturnFormat.retParam(maps).getBytes());            }            return ;        }else if(type==SessionType.WEB){            //用户已经在其他地方登录了,对于web,直接退出            logger.info("用户已经在其他地方登录了,登录类型为:web,直接退出");            try{                subject.logout();            }catch(Exception ex){                ex.printStackTrace();            }            response.setStatus(510);            Map<String, Object> maps=new HashMap<String, Object>();            maps.put("code", ErrorCode.NoLogin);            maps.put("msg", "用户已经在其他地方登录了");            response.getOutputStream().write(ReturnFormat.retParam(maps).getBytes());            return ;        }    }    //初始化    public void init(FilterConfig config) throws ServletException {        WebApplicationContext wac =WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext());        userSessionRedisService=(UserSessionRedisService)wac.getBean("userSessionRedisServiceImpl");        userService=(UserService)wac.getBean("userServiceImpl");    }}
  • 错误编码
/** * @类名 ErrorCode * @描述 错误编码 * @作者 zhuxl * @创建日期 2016年12月30日 下午4:28:38 */public enum ErrorCode {    /**     * ErrorCode NoLogin 未登录系统,需要退出     */    NoLogin,    /**     * ErrorCode Error 普通错误,具体错看msg     */    Error,    /**     * ErrorCode NoPermission 无权限访问     */    NoPermission,    /**     * ErrorCode LossParam 缺少参数     */    LossParam,    /**     * ErrorCode HasEntity 已经存在(唯一性存在)     */    HasEntity,    /**     * ErrorCode NoEntity 没有对应的实体     */    NoEntity,    /**     * ErrorCode HasLogin 已经登录了系统     */    HasLogin,    /**     * ErrorCode OK 成功     */    OK}
  • Api调用返回结果
/** * @类名 ApiMsg * @描述 Api调用返回 * @作者 zhuxl * @创建时间 2017-4-19下午02:21:33 */public class ApiMsg implements Serializable{    /**     * long serialVersionUID      */    private static final long serialVersionUID = -5590788323134429419L;    /**     * int code 编码 -1 未登录,1 成功,0 其他错误     */    private ErrorCode code;    /**     * String msg 信息     */    private String msg;    /**     * Object result 返回结果     */    private Object result;    public ErrorCode getCode() {        return code;    }    public void setCode(ErrorCode code) {        this.code = code;    }    public String getMsg() {        return msg;    }    public void setMsg(String msg) {        this.msg = msg;    }    public Object getResult() {        return result;    }    public void setResult(Object result) {        this.result = result;    }    public ApiMsg(ErrorCode code, String msg, Object result) {        super();        this.code = code;        this.msg = msg;        this.result = result;    }    public ApiMsg(Object result) {        this.code=ErrorCode.OK;        this.msg="成功";        this.result = result;    }}
  • 对于外部系统调用,将统一给出ApiMsg作为返回结果,拦截返回结果@ResponseBody
/** * @类名 ApiResponseBodyAdvice * @描述 对于外部系统调用,将统一给出ApiMsg作为返回结果 * @作者 zhuxl * @创建时间 2017-5-26下午05:18:39 */@ControllerAdvicepublic class ApiResponseBodyAdvice implements ResponseBodyAdvice<Object>{    @Override    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {        return true;    }    @Override    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType,            ServerHttpRequest request, ServerHttpResponse response) {        if(body instanceof ApiMsg){            return body;        }else{            String url=request.getURI().getPath();            if(url.matches("^.{0,}/api/\\w{1,}/\\w{1,}API.{0,}$")){                //对string类型做特殊处理                if(body instanceof String&& StringHttpMessageConverter.class.equals(selectedConverterType)){                    return JSONObject.toJSONString(new ApiMsg(body));                }else{                    return new ApiMsg(body);                }            }else{                return body;            }        }    }}
  • 全局异常处理,对于前台调用返回510错误,外部接口调用还是返回200正常,由code代表是否正常,msg具体错误原因
/** * @类名 GlobalExceptionHandler * @描述 全局异常处理 * @作者 zhuxl * @创建时间 2017-5-26下午02:25:15 */@ControllerAdvicepublic class GlobalExceptionHandler {    private Logger logger=Logger.getLogger(GlobalExceptionHandler.class);    /**     * handleException     * @描述: 处理PhyException异常     * @作者: zhuxl     * @创建时间: 2016-7-14下午07:45:16     * @param request     * @param response     * @param phyException     * @return     */    @ExceptionHandler(PhyException.class)    public @ResponseBody ApiMsg handleException(PhyException phyException,HttpServletRequest request,HttpServletResponse response){        String url=request.getRequestURI();        if(url.matches("^.{0,}/api/\\w{1,}/\\w{1,}API.{0,}$")){            return new ApiMsg(phyException.getCode(),phyException.getCode()==ErrorCode.NoLogin?"ssid失效或者未登录":phyException.getMessage(),null);        }else{            response.setStatus(510);            return new ApiMsg(phyException.getCode(),phyException.getMessage(),null);        }    }    /**     * 用户未登录系统     * @return     */    @ExceptionHandler(UnauthenticatedException.class)    public @ResponseBody ApiMsg handleUnauthenticatedException(HttpServletRequest request,HttpServletResponse response){        String url=request.getRequestURI();        if(url.matches("^.{0,}/api/\\w{1,}/\\w{1,}API.{0,}$")){            return new ApiMsg(ErrorCode.NoLogin,"token失效或者未登录",null);        }else{            Subject subject=SecurityUtils.getSubject();            if(subject==null||!subject.isAuthenticated()){                response.setStatus(510);                return new ApiMsg(ErrorCode.NoLogin,"用户未登录系统",null);            }else{                response.setStatus(510);                return new ApiMsg(ErrorCode.NoLogin,"用户已经登录系统",null);            }        }    }    /**用户无权限访问     * @return     */    @ExceptionHandler(AuthorizationException.class)    public @ResponseBody ApiMsg handleAuthorizationException(HttpServletRequest request,HttpServletResponse response){        String url=request.getRequestURI();        if(url.matches("^.{0,}/api/\\w{1,}/\\w{1,}API.{0,}$")){            return new ApiMsg(ErrorCode.NoPermission,"无权限访问",null);        }else{            response.setStatus(510);            return new ApiMsg(ErrorCode.NoPermission,"无权限访问",null);        }    }    /**     * handleOtherException     * @描述 其他错误返回       * @作者 zhuxl     * @创建时间 2017年2月20日 下午5:45:50     * @return     */    @ExceptionHandler(value={Exception.class,Error.class})    public @ResponseBody ApiMsg handleOtherException(Throwable throwable,HttpServletRequest request,HttpServletResponse response){        String url=request.getRequestURI();        logger.error("系统出错",throwable);        if(url.matches("^.{0,}/api/\\w{1,}/\\w{1,}API.{0,}$")){            return new ApiMsg(ErrorCode.Error,"系统出错了",null);        }else{            response.setStatus(510);            return new ApiMsg(ErrorCode.Error,"系统出错了",null);        }    }}
  • API返回结果之前,自动退出,使得外部接口调用无session信息
/** * @类名 ApiInterceptor * @描述 对API调用的接口进行拦截,退出其登录状态(前台将无JSESSIONID) * @作者 zhuxl * @创建时间 2017-4-21上午10:40:37 */public class ApiInterceptor extends HandlerInterceptorAdapter{    @Override    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {        String url=request.getRequestURI();        if(url.matches("^.{0,}/api/\\w{1,}/\\w{1,}API.{0,}$")){            org.apache.shiro.subject.Subject curUser=SecurityUtils.getSubject();            if(curUser.isAuthenticated()||curUser.isRemembered()){                try{                    curUser.logout();                }catch (Exception e) {                    e.printStackTrace();                }            }            /*HttpSession session=request.getSession(false);            if(session!=null){                session.invalidate();            }*/        }    }}
  • 外部接口登录
/**     * loginApi     * @描述 外部接口登录,将返回一个token,token(32位)一天有效,过期后将无法使用,需要重新获取,重复调用将重复生成     * @作者 zhuxl     * @创建时间 2017-4-19下午03:21:45     * @param nameAndPwd     * @return     */    @RequestMapping(value = {"loginAPI"}, method = RequestMethod.POST)    public String loginApi(@Valid @RequestBody LoginNameAndPwd nameAndPwd,BindingResult result){        if(result.hasFieldErrors()){            throw new PhyException(ErrorCode.Error,result.getFieldError().getDefaultMessage());        }        User user2 = userService.findByLoginName(nameAndPwd.getLoginName());        if (user2 == null) {            throw new PhyException(ErrorCode.Error,"当前登录用户不存在");        }        if (user2.getPassword().equals(CryptUtil.encrypt(nameAndPwd.getTextPwd(), user2.getSalt()))) {                String token=UUID.randomUUID().toString().replace("-", "");                userSessionRedisService.add(SessionType.API,token, user2.getId(),86400000L);                logger.info(new LogModel(true, "user api login system success,loginName:"+user2.getLoginName()));                return token;        } else {            throw new PhyException(ErrorCode.Error,"登录名或者密码不正确");        }    }
  • 对于俩种都需要的,添加一个请求url即可。
/**     * findMyStation     * @描述   查找我的监测站(包括状态)     * @作者 zhuxl     * @创建时间 2017年2月6日 下午1:51:04     * @param session     * @return     */    @RequiresUser    @RequestMapping(value={"findMyStation","findMyStationAPI"},method=RequestMethod.GET)    public List<StationMapTag> findMyStation(HttpSession session){        User user=(User)session.getAttribute("user");        if(user==null){            throw new PhyException(ErrorCode.NoLogin,"未登录系统");        }        Role role=user.getRole();        List<Station> stations=new ArrayList<Station>();        List<StationMapTag> stationTags=new ArrayList<StationMapTag>();        if(role.getRoleName().equals("admin")){            stations=stationService.findAll();        }else{            stations=stationService.findByArea(user.getArea().getId());        }        //此处省略    }
  • spring mvc配置如下:
<context:component-scan base-package="com.phy.smartcity.eps.*.openapi,com.phy.smartcity.eps.filter">    </context:component-scan>    <!-- Json Converter -->    <bean id="jacksonMessageConverter"        class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">    </bean>    <!-- <bean id="stringMessageConverter"        class="org.springframework.http.converter.StringHttpMessageConverter">        <constructor-arg  value="UTF-8"/>    </bean> -->    <!-- validator -->    <mvc:interceptors>        <bean class="com.phy.smartcity.eps.filter.ApiInterceptor"></bean>    </mvc:interceptors>    <bean id="validator"        class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">        <property name="providerClass" value="org.hibernate.validator.HibernateValidator" />        <!-- 如果不加默认到 使用classpath下的 ValidationMessages.properties -->        <property name="validationMessageSource" ref="messageSource" />    </bean>    <bean id="messageSource"        class="org.springframework.context.support.ReloadableResourceBundleMessageSource">        <property name="basenames">            <list>                <value>classpath:ValidationMessages_zh_CN</value>                <value>classpath:org/hibernate/validator/ValidationMessages_zh_CN</value>            </list>        </property>        <property name="useCodeAsDefaultMessage" value="false" />        <property name="defaultEncoding" value="UTF-8" />        <property name="cacheSeconds" value="60" />    </bean>    <!-- Handler Adapter -->    <bean        class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">        <property name="messageConverters">            <list>            <!--    <ref bean="stringMessageConverter" /> -->                <ref bean="jacksonMessageConverter" />            </list>        </property>    </bean>    <!-- file uploader support -->    <bean id="multipartResolver"        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">        <property name="defaultEncoding" value="UTF-8" />        <property name="maxUploadSize" value="10485760" />    </bean>    <mvc:annotation-driven validator="validator" />    <aop:config proxy-target-class="true"></aop:config>    <bean        class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">        <property name="securityManager" ref="securityManager" />    </bean>
原创粉丝点击