AnonymousController.java 17.9 KB
package com.tianbo.warehouse.controller;

import com.alibaba.fastjson.JSON;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.google.code.kaptcha.impl.DefaultKaptcha;

import com.thoughtworks.xstream.core.util.Base64Encoder;
import com.tianbo.warehouse.controller.response.ResultJson;
import com.tianbo.warehouse.dao.UserRoleMapper;
import com.tianbo.warehouse.model.*;
import com.tianbo.warehouse.security.filter.JwtTokenUtil;
import com.tianbo.warehouse.security.login.TokenUtils;
import com.tianbo.warehouse.service.PermissionService;
import com.tianbo.warehouse.service.RoleService;

import com.tianbo.warehouse.service.UserService;
import com.tianbo.warehouse.util.MapToJsonUtil;
import com.tianbo.warehouse.util.RedisUtils;

import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;

@Slf4j
@RestController()
@RequestMapping("/anonymous")
public class AnonymousController {

    @Autowired
    RoleService roleService;

    @Autowired
    private PermissionService permissionService;

    @Autowired
    RedisUtils redisUtils;

    @Autowired
    private  DefaultKaptcha captchaProducer;

    @Autowired
    RestTemplate restTemplate;

    @Value("${sso.url}")
    private String SSOUrl;

    @Value("${jwt.max-alive}")
    protected Integer jwtMaxAlive;

    @Autowired
    UserService userService;

    @Resource
    UserRoleMapper userRoleMapper;

    /**
     * SSO验证服务票据响应属性名
     */
    private static final String SERVICE_RESPONESE = "serviceResponse";
    private static final String AUTHENTICATION_SUCCESS = "authenticationSuccess";
    private static final String LOGIN_NAME = "LOGIN_NAME";
    private static final String ATTRIBUTES = "attributes";
    private static final String USER_ID = "USER_ID";

    /**
     * 配置匿名者可以访问的路由,并更新到redis,匿名者默认可以访问的role_name =ROLE_anonymous
     * 此方法会将所有符合权限组名=ROLE_anonymous的权限更新到redis中,供gateway调用判断权限
     * @return
     */
    @PostMapping("initAnonymousRoute")
    public ResultJson initAnonymousRoute(){
        List<ROLE> list = roleService.getROLE_anonymousPermList();
        String json = JSON.toJSONString(list);
        boolean result= redisUtils.set("ROLE_anonymous_routers", json,0);
        return result  ? new ResultJson("200","匿名者权限配置成功") :new ResultJson("500","匿名者权限配置失败");
    }

    /**
     * 生成验证码
     */
    @RequestMapping(value = "/randCode")
    public ResultJson getRandCode(){
        // 生成自定义验证码文字
        Random random = new Random();
        int num1 = random.nextInt(100);
        int num2 = random.nextInt(100);
        int sum = num1 + num2;
        System.out.println("验证码答案为: = " + sum);
        String capText = num1 + " + " + num2 + " = ";
        // 获取验证码上的文字
        //        String capText = captchaProducer.createText();
        //  将文件渲染到图片上
        BufferedImage bi = captchaProducer.createImage(capText);
        ByteArrayOutputStream outputStream = null;
        outputStream = new ByteArrayOutputStream();
        Base64Encoder encoder = new Base64Encoder();
        Map<String,Object> map = new HashMap<>();
        String verifyToken = "";
        try {
            verifyToken =  UUID.randomUUID().toString();
            redisUtils.set(Token.VERIFY_TOKEN_KEY + verifyToken,String.valueOf(sum),120);
            ImageIO.write(bi, "jpeg", outputStream);
            map.put("verifyImg","data:image/jpeg;base64,"+encoder.encode(outputStream.toByteArray()));
        } catch (IOException e) {
            e.printStackTrace();
            return new ResultJson("500","verify get error");
        }
        return new ResultJson("200","verify get ok",map,verifyToken);

    }

    @ApiOperation(value = "查询用户列表及信息", notes = "查询用户列表及单个用户信息")
    @RequestMapping("ssoTicket")
    public ResultJson ssoLogin(@RequestParam("ticket") String ticket,
                               @RequestParam("myWebLoginUrl") String myWebLoginUrl
    ){
        try {
            log.info("[SSO-AUTH-TICKET]-开始单点登录票据认证-[{}]",ticket);
            // 构建接口地址
            String url = SSOUrl+"?format=json&service="
                    + myWebLoginUrl
                    + "&ticket=" + ticket;

            // 使用RestTemplate调用接口
            RestTemplate restTemplate = new RestTemplate();
            /**
             * 单点登录认证返回实体类
             * {
             *   "serviceResponse" : {
             *     "authenticationSuccess" : {
             *       "user" : "zhangyf",
             *       "attributes" : {
             *         "isFromNewLogin" : [ false ],
             *         "authenticationDate" : [ 1.614564403617E9 ],
             *         "successfulAuthenticationHandlers" : [ "pwd" ],
             *         "USER_ID" : "8a0162a628aa4049a7840d75378f1a91",
             *         "USER_NAME" : "张炎锋",
             *         "extend" : [ ],
             *         "credentialType" : "UsernamePasswordCredential",
             *         "samlAuthenticationStatementAuthMethod" : "urn:oasis:names:tc:SAML:1.0:am:password",
             *         "ipTerritory" : "",
             *         "authenticationMethod" : "pwd",
             *         "equipType" : "pc",
             *         "clientIp" : "172.19.0.17",
             *         "isDefaultPwd" : "false",
             *         "longTermAuthenticationRequestTokenUsed" : [ false ],
             *         "LOGIN_NAME" : "zhangyf",
             *         "MOBILE" : "18739902467"
             *       }
             *     }
             *   }
             * },
             */
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            String responseBody = responseEntity.getBody();
            log.info("[SSO-RESPONSE]-[{}]",responseBody);

            try {
                JSONObject jsonObject = JSONObject.parseObject(responseBody);
                USERS user = parseSSOObject(jsonObject);
                if (StringUtils.isNotEmpty(user.getUsername())){
                    log.info("[SSO-USER]-获取到sso用户名-[{}]",user.getUsername());
                    USERS loginUser = userService.loadByUsername(user.getUsername());
                    if (loginUser!=null && loginUser.getUserId()>-1){
                        log.info("[SSO-AUTH-TICKET]-从认证中心获取到用户[{}]信息,开始设置系统登录认证token",user.getUsername());
                        user.setUserId(loginUser.getUserId());
                        user.setUsername(loginUser.getUsername());
                        user.setUserface(loginUser.getUserface());
                        user.setUserId(loginUser.getUserId());
                        user.setRealname(loginUser.getRealname());
                        user.setCompanyId(loginUser.getCompanyId());
                        user.setCompanyName(loginUser.getCompanyName());
                        user.setCompanyInfo(loginUser.getCompanyInfo());
                        user.setUserStatus(loginUser.getUserStatus());
                        user.setState(loginUser.getState());
                    }else {
                        log.info("[SSO-INSERT]-用户不存在,新增SSO用户");
                        user.setPassword("initSSO");
                        user.setUserStatus(2);
                        user.setState(true);
                        Integer userId = userService.insertSelective(user);
                    }
                    //设置用户的TOKEN的有效时间,时间配置在配置文件中设置
                    int expirationSeconds = 3600*24*7;
                    String jwtToken = JwtTokenUtil.generateToken(user.getUsername(), jwtMaxAlive);
                    user.setToken(jwtToken);
                    //这里将登录成功的[user]对象数据写入redis缓存,KEY为token value为user的JSON对象
                    String json = JSON.toJSONString(loginUser);
                    redisUtils.set(jwtToken, json,expirationSeconds);
                    redisUtils.set(Token.USER_TOKEN_KEY + user.getUsername(),jwtToken,expirationSeconds);
                    Map<String,Object> menuMap =  permissionService.getUserMenus(user.getUserId());
                    return new ResultJson("200","单点登录认证成功",user);
                }
            } catch (JSONException e) {
                e.printStackTrace();
                log.error("[SSO-AUTH-TICKET-ERR]-单点登录票据解析异常",e);
                log.info("[SSO-AUTH-TICKET-ERR]-单点登录票据解析异常",e);
                return new ResultJson("400","单点登录票据解析异常",e.getMessage());
            }
        }catch (Exception e){
            e.printStackTrace();
            log.error("[SSO-AUTH-TICKET-ERR]-单点登录票据认证异常",e);
            log.info("[SSO-AUTH-TICKET-ERR]-单点登录票据认证异常",e);
        }
        return new ResultJson("401","单点登录票据认证失败");
    }

    /**
     * 解析单点认证返回的信息
     * @param ssoResp 返回实体类
     * @return 用户类
     */
    private USERS parseSSOObject(JSONObject ssoResp){
        USERS user = new USERS();
        // 根节点
        if (ssoResp.containsKey(SERVICE_RESPONESE)){
            JSONObject root = ssoResp.getJSONObject(SERVICE_RESPONESE);

            //成功节点
            if (root.containsKey(AUTHENTICATION_SUCCESS)){
                JSONObject auth = root.getJSONObject(AUTHENTICATION_SUCCESS);
                //用户名获取
                String userName = auth.getString("user");

                //用户其他属性
                JSONObject attributes = auth.getJSONObject(ATTRIBUTES);
                String loginName = attributes.getString(LOGIN_NAME);
                String userId = attributes.getString(USER_ID);
                String realName = attributes.getString("USER_NAME");
//                String password = attributes.getString("PWD");
                user.setUsername(loginName);
//                user.setPassword(password);
                log.info("[SSO-AUTH-TICKET-INFO]-用户:{}/{}/{}",loginName,realName);
//                user.setUserId(userId);
            }
        }
        return user;
    }

    @PostMapping(value = "/ssoUserSynchronization")
    @ResponseBody
    public ResultMessage ssoUserSynchronization(@RequestBody String jsonString, @RequestHeader Map<String,String> headers, HttpServletRequest request) throws Exception{
        log.info("[SSO-USER-SYNCHRONIZATION]-userJson:[{}],headers:[{}]",jsonString,headers);
        return new ResultMessage(200,"资源同步测试成功");
    }

    /**
     * 资源同步
     * @param map
     * @return
     */
    @PostMapping(value = "/userSynchronization")
    @ResponseBody
    public ResultMessage userSynchronization(@RequestBody Map<String, Object> map, @RequestHeader Map<String, String> headers,HttpServletRequest request){
        log.info("[SSO-资源同步]-参数打印:\n{}",map.toString());
        headers.forEach((key,value)->{
            log.info("[SSO-USER-SYNCHRONIZATION-HEADER-INFO]-key:{},value:{}",key,value);
        });

        //IP白名单
        List<String> ipWhiteList = Arrays.asList(
                "10.5.14.108",
                "10.5.14.109",
                "10.5.14.110",
                "127.0.0.1"
        );

        String requestRemoteAddr = request.getHeader("X-Forwarded-For");

        if (StringUtils.isEmpty(requestRemoteAddr)){
            requestRemoteAddr = request.getRemoteAddr();
        }else {
            requestRemoteAddr = requestRemoteAddr.split(",")[0];
        }

        if (ipWhiteList.contains(requestRemoteAddr)){
            log.info("[SSO-USER-SYNCHRONIZATION-IPWhiteList]-ipWhiteList:[{}]",requestRemoteAddr);
        }else {
            log.info("[SSO-USER-SYNCHRONIZATION-IPWhiteList]-ip:[{}]白名单验证失败,访问IP不在白名单内",requestRemoteAddr);
            return new  ResultMessage(400,"访问IP不在白名单内,验证失败.ip:"+requestRemoteAddr);
        }

        // 用headers Map 取值 key 键值时 key会全部转为小写 在springboot 中
        if (headers.containsKey("once") && headers.containsKey("ts") && headers.containsKey("appkey") && headers.containsKey("signmethod") && headers.containsKey("signdata")){
            String appAuthKey = "4768711539138560" ;
            if (appAuthKey.equals(headers.get("appkey"))){
                String bodyData = JSON.toJSONString(map);
                String appPwd = "ce10ec6cc310966de5264994817a0f7c1b2b9e3b";
                log.info("[SSO-INFO]-apppwd:{}",appPwd);
                StringBuilder sb = new StringBuilder();
                //签名验证格式拼接

                sb.append("appKey=").append(headers.get("appkey"))
                        .append("&ts=").append(headers.get("ts"))
                        .append("&once=").append(headers.get("once"))
                        .append("&signMethod=").append(headers.get("signmethod"))
                        .append("&bodyData=").append(bodyData);
                String signAuthURI = sb.toString();
                // 签名生成
                try{
                    String signData = TokenUtils.getSignature(appPwd, signAuthURI);
                    // 签名验证
                    if (signData.equals(headers.get("signdata"))){
                        log.info("[SSO-USER-SYNCHRONIZATION-HEADER-SUCCESS]-头部签名验证成功");
                    }else {
                        log.error("[SSO-USER-SYNCHRONIZATION-HEADER-AUTHFAIL]-头部签名验证失败");
//                        return new  ResultMessage(400,"app签名验证失败");
                    }
                }catch (Exception e){
                    log.error("[SSO-USER-SYNCHRONIZATION-HEADER-AUTH-ERR]-",e);
//                    return new  ResultMessage(400,"app签名验证出错"+e.getMessage());
                }

            }else {
                log.error("[SSO-USER-SYNCHRONIZATION-HEADER-FAILD],key:{},不为验证key:4768711539138560",headers.get("appKey"));
//                return new  ResultMessage(400,"appKey验证失败");
            }
        }else {
            log.error("[SSO-USER-SYNCHRONIZATION-HEADER-FAILD]-缺少必要头部验证信息");
//            return new  ResultMessage(400,"缺少必要头部验证信息,app验证失败");
        }



        //获取action的值,判断是push数据还是删除数据
        String action = map.get("action").toString();
        if ("user".equals(map.get("resType").toString())){
            MapToJsonUtil jsonUtil = new MapToJsonUtil();
            List<SSOUserData> list = jsonUtil.mapToList(map, SSOUserData.class, "data");
            // 判断该用户是否存在
            int i = 0;
            for (SSOUserData userData : list){
                log.info("用户信息:{}", userData);
                USERS loginUser = userService.loadByUsername(userData.getLOGIN_NAME());
                USERS users = new USERS();
                users.setUsername(userData.getLOGIN_NAME());
                users.setPassword(userData.getPWD());
                //todo:统一认证的用户ID与我们的数据类型不匹配 暂时不同步
                users.setMobilephone(userData.getMOBILE());
                users.setEmail(userData.getEMAIL());
                users.setRealname(userData.getUSER_NAME());
                users.setCompanyId(73);
                users.setUpdatetime(new Date());
                if (StringUtils.isEmpty(users.getUsername())){
                    log.info("[资源同步]-缺少lognin_name字段信息");
                    return new  ResultMessage(400,"缺少lognin_name字段信息");
                }
                if ("push".equals(action)) {
                    if (loginUser != null){
                        log.info("通过账号为条件更新");
                        if ("-1000".equals(userData.getJOB_STATUS())){
                            users.setState(false);
                        }else {
                            users.setState(true);
                        }
                        i = userService.updateByUsernameSelective(users);
                    }else {
                        if ( StringUtils.isEmpty(users.getPassword())){
                            log.info("[资源同步]-缺少pwd字段信息");
                            return new  ResultMessage(400,"缺少pwd字段信息");
                        }
                        users.setCreattime(new Date());
                        log.info("账号统一认证用户信息不存在可以 [新增]");
                        //接口返回userid,roleid73 等于转关运抵管理员
                        int userId = userService.insertSelective(users);
                        UserRole userRole = new UserRole(userId,73);
                        userRoleMapper.insertSelective(userRole);
                        //todo: 默认权限分配
                        i= userId;
                    }
                }else if ("recycle".equals(action)){
                    log.info("[资源同步]-接收到用户撤销指令,开始删除用户:{},userid:{}",users.getUsername(),userData.getUSER_ID());
                    i= userService.deleteByUsername(users.getUsername());
                }
            }

            return  i > 0 ? new ResultMessage(200,"资源同步成功"):new ResultMessage(200, "资源同步失败");
        }
        return new ResultMessage(200,"不是用户信息");
    }
}