api接口的实现

我们目前有一个系统,使用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 maps=new HashMap();
            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 maps=new HashMap();
                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 maps=new HashMap();
            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
 */
@ControllerAdvice
public class ApiResponseBodyAdvice implements ResponseBodyAdvice<Object>{

    @Override
    public boolean supports(MethodParameter returnType, Class> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> 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
 */
@ControllerAdvice
public 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 findMyStation(HttpSession session){
        User user=(User)session.getAttribute("user");
        if(user==null){
            throw new PhyException(ErrorCode.NoLogin,"未登录系统");
        }
        Role role=user.getRole();
        List stations=new ArrayList();
        List stationTags=new ArrayList();
        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>
    
    <bean id="jacksonMessageConverter"
        class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
    bean>
    
    
    <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" />
        
        <property name="validationMessageSource" ref="messageSource" />
    bean>
    <bean id="messageSource"
        class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>classpath:ValidationMessages_zh_CNvalue>
                <value>classpath:org/hibernate/validator/ValidationMessages_zh_CNvalue>
            list>
        property>
        <property name="useCodeAsDefaultMessage" value="false" />
        <property name="defaultEncoding" value="UTF-8" />
        <property name="cacheSeconds" value="60" />
    bean>
    
    <bean
        class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
            
                <ref bean="jacksonMessageConverter" />
            list>
        property>
    bean>
    
    <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>

你可能感兴趣的:(Spring,Java)