These days, I checked the global permissions of the write project, using Zuul as the service gateway, and did the check in Zuul’s prefilter.

At present, there are many ways to verify tokens, including generating tokens and storing them in Redis or databases, and many using JWT (JSON Web Token).

To be honest, I don’t have much experience in this area, and I’m in a rush for a project, so I used a simple solution first.

After a successful login, the Token is returned to the front-end and saved in Redis. Each time the request interface retrieves the Token from Cookie or Header, and retrieves the stored Token from Redis to check whether the Token is consistent.

I know it’s not perfect, and there are security issues, and it’s vulnerable to hijacking. However, the current strategy is to finish the project functions first, and then slowly optimize them after the launch, without too much detail on one function point, so as to ensure the project progress will not be too slow.

Project address: github.com/cachecats/c…

This paper will be divided into four parts

  1. The login logic
  2. AuthFilter Indicates the pre-filter verification logic
  3. Utility class
  4. demonstration

Login logic

After a successful login, the generated Token is stored in Redis. The key is TOKEN_userId. If the user’s userId is 222222, the key is TOKEN_222222; Value is the generated Token.

Post only the Serive code for login

@Override
public UserInfoDTO loginByEmail(String email, String password) {

    if (StringUtils.isEmpty(email) || StringUtils.isEmpty(password)) {
        throw new UserException(ResultEnum.EMAIL_PASSWORD_EMPTY);
    }

    UserInfo user = userRepository.findUserInfoByEmail(email);
    if (user == null) {
        throw new UserException(ResultEnum.EMAIL_NOT_EXIST);
    }
    if(! user.getPassword().equals(password)) {throw new UserException(ResultEnum.PASSWORD_ERROR);
    }

    // Generate the token and save it in Redis
    String token = KeyUtils.genUniqueKey();
    // Store the token in Redis. The key is TOKEN_ user ID and the value is token
    redisUtils.setString(String.format(RedisConsts.TOKEN_TEMPLATE, user.getId()), token, 2l, TimeUnit.HOURS);

    UserInfoDTO dto = new UserInfoDTO();
    BeanUtils.copyProperties(user, dto);
    dto.setToken(token);

    return dto;
}
Copy the code

AuthFilter Prefilter

AuthFilter inherits from ZuulFilter, and must implement ZuulFilter’s four methods.

FilterType () : indicates the type of Filter. Prefilters return PRE_TYPE

FilterOrder (): Indicates the order of filters. The smaller the value, the earlier the Filter is executed. This is written pre_decoration_filter_order-1, which is the official recommendation.

ShouldFilter (): Whether it should be filtered. Returns true for filtering, false for not filtering. You can use this method to determine which interfaces do not need filtering. This example excludes the registration and login interfaces, and all other interfaces need filtering.

Run (): specifies the logic of the filter

For the convenience of the front-end, the token can be set to either cookie or header in consideration of providing services for PC, APP, applets and other platforms. The cookie is taken first, but the header is not taken from the cookie.

package com.solo.coderiver.gateway.filter;

import com.google.gson.Gson;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.solo.coderiver.gateway.VO.ResultVO;
import com.solo.coderiver.gateway.consts.RedisConsts;
import com.solo.coderiver.gateway.utils.CookieUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

/** * Permission authentication Filter * Registration and login interface does not Filter ** Authentication permission requires the front end to set the user's userId and token in Cookie or Header * Because token is stored in Redis, The Redis key is made up of userId and the value is token *. If no userId or token is found in either place, 401 is returned with no permission and a text prompt is given */
@Slf4j
@Component
public class AuthFilter extends ZuulFilter {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    // Exclude filtered URI addresses
    private static final String LOGIN_URI = "/user/user/login";
    private static final String REGISTER_URI = "/user/user/register";

    // No power limit prompt
    private static final String INVALID_TOKEN = "invalid token";
    private static final String INVALID_USERID = "invalid userId";

    @Override
    public String filterType(a) {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder(a) {
        return PRE_DECORATION_FILTER_ORDER - 1;
    }

    @Override
    public boolean shouldFilter(a) {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        log.info("uri:{}", request.getRequestURI());
        // The registration and login interfaces are not blocked. Other interfaces are blocked to verify the token
        if (LOGIN_URI.equals(request.getRequestURI()) ||
                REGISTER_URI.equals(request.getRequestURI())) {
            return false;
        }
        return true;
    }

    @Override
    public Object run(a) throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        // Get the token from the cookie first, then get the token from the header
        // Retrieve token from Cookie by utility class
        Cookie tokenCookie = CookieUtils.getCookieByName(request, "token");
        if (tokenCookie == null || StringUtils.isEmpty(tokenCookie.getValue())) {
            readTokenFromHeader(requestContext, request);
        } else {
            verifyToken(requestContext, request, tokenCookie.getValue());
        }

        return null;
    }

    /** * read the token from the header and verify */
    private void readTokenFromHeader(RequestContext requestContext, HttpServletRequest request) {
        // Read from the header
        String headerToken = request.getHeader("token");
        if (StringUtils.isEmpty(headerToken)) {
            setUnauthorizedResponse(requestContext, INVALID_TOKEN);
        } else{ verifyToken(requestContext, request, headerToken); }}/** * Check the token from Redis */
    private void verifyToken(RequestContext requestContext, HttpServletRequest request, String token) {
        // We need to retrieve the userId from the cookie or header to verify the validity of the token. Since there is one token for each user, the key in Redis is TOKEN_userId
        Cookie userIdCookie = CookieUtils.getCookieByName(request, "userId");
        if (userIdCookie == null || StringUtils.isEmpty(userIdCookie.getValue())) {
            // Get the userId from the header
            String userId = request.getHeader("userId");
            if (StringUtils.isEmpty(userId)) {
                setUnauthorizedResponse(requestContext, INVALID_USERID);
            } else {
                String redisToken = stringRedisTemplate.opsForValue().get(String.format(RedisConsts.TOKEN_TEMPLATE, userId));
                if(StringUtils.isEmpty(redisToken) || ! redisToken.equals(token)) { setUnauthorizedResponse(requestContext, INVALID_TOKEN); }}}else {
            String redisToken = stringRedisTemplate.opsForValue().get(String.format(RedisConsts.TOKEN_TEMPLATE, userIdCookie.getValue()));
            if(StringUtils.isEmpty(redisToken) || ! redisToken.equals(token)) { setUnauthorizedResponse(requestContext, INVALID_TOKEN); }}}/** * Set 401 to no permission */
    private void setUnauthorizedResponse(RequestContext requestContext, String msg) {
        requestContext.setSendZuulResponse(false);
        requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());

        ResultVO vo = new ResultVO();
        vo.setCode(401);
        vo.setMsg(msg);
        Gson gson = newGson(); String result = gson.toJson(vo); requestContext.setResponseBody(result); }}Copy the code

Third, tools

MD5 utility class

package com.solo.coderiver.user.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/** * Generate MD5 tool class */
public class MD5Utils {

    public static String getMd5(String plainText) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(plainText.getBytes());
            byte b[] = md.digest();

            int i;

            StringBuffer buf = new StringBuffer("");
            for (int offset = 0; offset < b.length; offset++) {
                i = b[offset];
                if (i < 0)
                    i += 256;
                if (i < 16)
                    buf.append("0");
                buf.append(Integer.toHexString(i));
            }
            / / 32 bit encryption
            return buf.toString();
            // 16-bit encryption
            //return buf.toString().substring(8, 24);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null; }}/** * The encryption and decryption algorithm performs encryption once and decryption twice */
    public static String convertMD5(String inStr){

        char[] a = inStr.toCharArray();
        for (int i = 0; i < a.length; i++){
            a[i] = (char) (a[i] ^ 't');
        }
        String s = new String(a);
        returns; }}Copy the code

The utility class that generates keys

package com.solo.coderiver.user.utils;

import java.util.Random;

public class KeyUtils {

    /** * produces a unique key */
    public static synchronized String genUniqueKey(a){
        Random random = new Random();
        int number = random.nextInt(900000) + 100000;
        String key = System.currentTimeMillis() + String.valueOf(number);
        returnMD5Utils.getMd5(key); }}Copy the code

4. Demonstration and verification

Start the API_gateway project on port 8084, along with the User project.

Postman is used to access the login interface through the gateway. Because the filter excludes the login and registration interfaces, the tokens of these two interfaces are not verified.

As you can see, visit the address http://localhost:8084/user/user/login login successfully and returned to the user information and token.

The user id is 111111, so the key is TOKEN_111111, and the value is the token value that was just generated

To request another interface, it should go through the filter.

Header does not pass token or userId. Return 401

Pass token but not userId, return 401 and prompt invalid userId

Both token and userId are passed, but the token is incorrect. 401 is returned and invalid token is displayed

If the token and userId are correct, the request succeeds

The above is the simple Token verification, if there is a better solution, welcome to the comment section


Code from the open source project CodeRiver, committed to creating a full platform full stack boutique open source project.

Coderiver is a platform for programmers and designers to collaborate on projects. Whether you are a front-end, back-end, mobile developer, designer, or product manager, you can publish your project on the platform and collaborate with like-minded partners to complete your project.

Coderiver river Code is similar to programmer inn, but its main purpose is to facilitate the technical exchange between talents in various fields, grow together, and complete projects with multiple people. There is no money involved.

It is planned to make a full platform full stack project including PC side (Vue, React), mobile H5 (Vue, React), ReactNative mixed development, Android native, wechat small program, Java back end. Welcome to follow.

Project address: github.com/cachecats/c…


Your encouragement is my biggest power forward, welcome to praise, welcome to send small stars ✨ ~