Implementation of API Authentication, Authorization, and Access Control

By | August 23, 2021

Overview

If the API interface docked with the front-end is captured by a third party and maliciously tampered with the parameters, it may cause data leakage or even tamper with the data. I mainly focus on the three parts of timestamp, token, and signature to ensure the security of the API interface. 1. After the user successfully logs in to the site, the server will return a token, and any operation of the user must carry this parameter, which can be directly placed in the header. 2. The client uses the parameters to be sent and the token to generate a signature sign, which is sent to the server as a parameter, and the server uses the same method to generate the sign to check whether it has been tampered with. 3. But there is still a problem, which may be malicious and unrestricted access. At this time, we need to introduce a timestamp parameter, which is invalid if it times out. 4. The server needs to verify the token, signature, and timestamp. Only when the token is valid and the timestamp has not expired can the signature be valid.

Open interface

There are no restrictions, simple and rude access methods. Such interface methods are generally in the open application platform, check the weather, check the express delivery, as long as you enter the correct corresponding parameter call, you can get the information you need, and we can modify it at will The parameter value.

@RestController
@RequestMapping("/token")
public class TokenSignController {

    @Autowired
    private TokenSignService tokenSignService;

    @RequestMapping(value = "openDemo",method = RequestMethod.GET)
    public List<PersonEntity> openDemo(int personId){
        return tokenSignService.getPersonList(personId);
    }
}

Token authentication acquisition After the user logs in successfully, a ticket value will be obtained, which is required for any subsequent interface access. We put it in redis, the validity period is 10 minutes, when the ticket is about to expire, it will continue to live without perception. Extend the usage time. If the user does not perform any operation within a period of time, he needs to log in to the system again. Extension: Remember the practice of token security certification

@RequestMapping(value = "login",method = RequestMethod.POST)
    public JSONObject login(@NotNull String username, @NotNull String password){
        return tokenSignService.login(username,password);
    }

Login operation, check whether there is this user, the user name and password match to log in successfully.

    public JSONObject login(String username,String password){
        JSONObject result = new JSONObject();
        PersonEntity personEntity = personDao.findByLoginName(username);
        if (personEntity == null || (personEntity != null && !personEntity.getPassword().equals(password))){
            result.put("success",false);
            result.put("ticket","");
            result.put("code","999");
            result.put("message",&quot;Username and password do not match&quot;);
 return result;
        }
        if (personEntity.getLoginName().equals(username) && personEntity.getPassword().equals(password)){
            String ticket = UUID.randomUUID().toString();
            ticket = ticket.replace("-","");
            redisTemplate.opsForValue().set(ticket,personEntity.getLoginName(),10L, TimeUnit.MINUTES);
            result.put("success",true);
            result.put("ticket",ticket);
            result.put("code",200);
            result.put("message",&quot;login successful&quot;);
 return result;
        }
        result.put("success",false);
        result.put("ticket","");
        result.put("code","1000");
        result.put("message",&quot;Unknown exception, please try again&quot;);
 return result;
    }

Sign

Splice all the parameters together, add the system secret key, and perform MD5 calculation to generate a signed signature to prevent the parameters from being maliciously tampered with. The background generates the secret key in the same way for signature comparison.
/**
 * @param request
 * @return
 */
    public static Boolean checkSign(HttpServletRequest request,String sign){
        Boolean flag= false;
        //Check if the sigin is expired
        Enumeration<?> pNames =  request.getParameterNames();
        Map<String, String> params = new HashMap<String, String>();
        while (pNames.hasMoreElements()) {
            String pName = (String) pNames.nextElement();
            if("sign".equals(pName)) continue;
            String pValue = (String)request.getParameter(pName);
            params.put(pName, pValue);
        }
        System.out.println(&quot;The present sign--&gt;&gt;&quot; + sign);
        System.out.println(&quot;Verified sign--&gt;&gt;&quot; + getSign(params,secretKeyOfWxh));
        if(sign.equals(getSign(params, secretKeyOfWxh))){
            flag = true;
        }
        return flag;
    }

Repeat visit
Introduce a timestamp parameter to ensure that the interface is only valid within one minute and needs to be consistent with the client time.
public static long getTimestamp(){
        long timestampLong = System.currentTimeMillis();

        long timestampsStr = timestampLong / 1000;

        return timestampsStr;
    }

It needs to be compared with the current server time. If it exceeds one minute, the request will be rejected to save the server query data consumption
Interceptor
Each request carries these three parameters, and we all need to verify it. Only when the three parameters meet our requirements can the data be returned or manipulated.
public class LoginInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws IOException {
        JSONObject jsonObject = new JSONObject();
        String ticket = request.getParameter("ticket");
        String sign = request.getParameter("sign");
        String ts = request.getParameter("ts");
        if (StringUtils.isEmpty(ticket) || StringUtils.isEmpty(sign) || StringUtils.isEmpty(ts)){
            jsonObject.put("success",false);
            jsonObject.put("message","args is isEmpty");
            jsonObject.put("code","1001");
            PrintWriter printWriter = response.getWriter();
            printWriter.write(jsonObject.toJSONString());
            return false;
        }
        //If redis has a ticket, it is considered a legitimate request
        if (redisTemplate.hasKey(ticket)){
            System.out.println(redisTemplate.opsForValue().getOperations().getExpire(ticket));
            String values = (String) redisTemplate.opsForValue().get(ticket);
            //Determine whether the ticket is about to expire, and continue the operation
            if (redisTemplate.opsForValue().getOperations().getExpire(ticket) != -2 && redisTemplate.opsForValue().getOperations().getExpire(ticket) < 20){
                redisTemplate.opsForValue().set(ticket,values,10L, TimeUnit.MINUTES);
            }
            System.out.println(SignUtils.getTimestamp());
            //Judging whether repeated access, there is a time window period for replay attacks
            if (SignUtils.getTimestamp() - Long.valueOf(ts) > 600){
                jsonObject.put("success",false);
                jsonObject.put("message","Overtime to connect to server");
                jsonObject.put("code","1002");
                PrintWriter printWriter = response.getWriter();
                printWriter.write(jsonObject.toJSONString());
                return false;
            }
            //Verify the signature
            if (!SignUtils.checkSign(request,sign)){
                jsonObject.put("success",false);
                jsonObject.put("message","sign is invalid");
                jsonObject.put("code","1003");
                PrintWriter printWriter = response.getWriter();
                printWriter.write(jsonObject.toJSONString());
                return false;
            }
            return true;
        }else {
            jsonObject.put("success",false);
            jsonObject.put("message","ticket is invalid,Relogin.");
            jsonObject.put("code","1004");
            PrintWriter printWriter = response.getWriter();
            printWriter.write(jsonObject.toJSONString());
        }
        return false;
    }
}

Access
Log in to the system first to obtain a valid ticket Generate a legal sign verification, get the test ts, and access openDemo to access it normally. You can also encrypt the parameters and replace http with https, so it won’t be expanded one by one.