springboot+vue3.0+token security verification

Posted by shadiadiph on Mon, 20 Dec 2021 02:51:27 +0100

catalogue

1, Explain

When the front and back ends are completely separated, the general idea of implementing token verification in Vue project is as follows (too lazy to type. This part quotes the content of the blogger User login and token authentication in Vue project):

1. When logging in for the first time, the front end calls the back end login interface and sends the user name and password

2. The back end receives the request, verifies the user name and password, and returns a token to the front end if the verification is successful

3. The front end gets the token, stores the token in localStorage and vuex, and jumps to the routing page

4. Each time the front end jumps the route, it will judge whether there is a token in the localStroage. If not, it will jump to the login page, and if there is, it will jump to the corresponding route page

5. Every time you call the back-end interface, you should add a token in the request header

6. The back-end judges whether there is a token in the request header. If there is a token, it will get the token and verify the token. If the verification is successful, it will return data. If the verification fails (for example, the token expires), it will return 401. If there is no token in the request header, it will also return 401

7. If the front end gets the status code 401, it clears the token information and jumps to the login page

2, Background (springboot)

1. Add dependent package

Add jwt dependency package in springboot project, which is mainly used to encrypt and decrypt token.

 <!--jwt rely on-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

2. Add token tool class

Add token tool class, TokenUtil

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.lin.springMVC.domain.User;

import java.util.Date;

public class TokenUtil {
    private static final long EXPIRE_TIME= 10*60*60*1000;
    private static final String TOKEN_SECRET="txdy";  //Key salt
    /**
     * Signature generation
     * @param user
     * @return
     */
    public static String sign(User user){
        String token = null;
        try {
            Date expiresAt = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            token = JWT.create()
                    .withIssuer("auth0")
                    .withClaim("userAccount", user.getUserAccount())
                    .withExpiresAt(expiresAt)
                    // HMAC256 encryption algorithm is used.
                    .sign(Algorithm.HMAC256(TOKEN_SECRET));
        } catch (Exception e){
            e.printStackTrace();
        }
        return token;
    }
    /**
     * Signature verification
     * @param token
     * @return
     */
    public static boolean verify(String token){
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer("auth0").build();
            DecodedJWT jwt = verifier.verify(token);
            System.out.println("Certification passed:");
            System.out.println("userAccount: " + jwt.getClaim("userAccount").asString());
            System.out.println("Expiration time:      " + jwt.getExpiresAt());
            return true;
        } catch (Exception e){
            return false;
        }
    }
}

3. Create interceptor

import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

@Component
public class TokenInterceptor implements HandlerInterceptor {
    private static final String token = "token#";
    @Autowired
    RedisHandler redisHandler;
    @Value("${api.api_prefix}")
    public String api_prefix;
    /**
     * Call before request processing, and enter this method before entering the Controller method
     * Return true to continue downward execution, and return false to cancel the current request
     * Login interception and permission resource control
     */
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
        String userAccount = null;
        String redirectUrl = "login";

        if ((httpServletRequest.getParameter("language") != null && !httpServletRequest.getParameter("language").equals("en"))) {
            redirectUrl += "_en";
        }
        Cookie[] cookies = httpServletRequest.getCookies();//Fetch from cookie
        String token1 = httpServletRequest.getHeader("Access-Token");// Fetch the token from the http request header
        if(token1 != null){
            boolean result = TokenUtil.verify(token1);
            if(result){
                System.out.println("Through interceptor");
                return true;
            }
        }
        try{
            JSONObject json = new JSONObject();
            json.put("msg","token verify fail");
            json.put("status","401");
            httpServletResponse.setCharacterEncoding("UTF-8");
            httpServletResponse.setContentType("application/json; charset=utf-8");
            PrintWriter out = null;
            out = httpServletResponse.getWriter();
            out.append(json.toString());
            return false;
        }catch (Exception e){
            e.printStackTrace();
            JSONObject json = new JSONObject();
            json.put("msg","error");
            json.put("status","500");
            httpServletResponse.setCharacterEncoding("UTF-8");
            httpServletResponse.setContentType("application/json; charset=utf-8");
            PrintWriter out = null;
            out = httpServletResponse.getWriter();
            out.append(json.toString());
            return false;
    }

}

4. Entrance interception

Configuration entry interception, ShiroConfiguration class

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.Filter;

/**
 * shiro Configuration class
 */
@Configuration
public class ShiroConfiguration {
    /**
     * Shiro Name of the Web filter Factory: shiroFilter
     */
    @Bean(name = "shiroFilter")
    //Equivalent to < bean id = "shirofilter" class = "org. Apache. Shiro. Spring. Web. Shirofilterfactorybean" >
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        //for defining the master Shiro Filter
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //Sets the application {@code SecurityManager} instance to be used by the constructed Shiro Filter
        //This is a required property - failure to set it will throw an initialization exception.
        shiroFilterFactoryBean.setSecurityManager(securityManager);//<property name="securityManager" ref="securityManager"/>
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        //Most implementations subclass one of the
        //{@link AccessControlFilter}, {@link AuthenticationFilter}, {@link AuthorizationFilter} classes to simplify things,
        //and each of these 3 classes has configurable properties that are application-specific.
        filterMap.put("authc", new AjaxPermissionsAuthorizationFilter());//Add filter
        shiroFilterFactoryBean.setFilters(filterMap);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();//Equivalent to < property name = "filterchaindefinitions" >
        filterChainDefinitionMap.put("/", "anon");
//        filterChainDefinitionMap.put("/debug/**", "anon");
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/register", "anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
    }

5. Configure cross domain

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration corsConfiguration = new CorsConfiguration();
        /*Allow requests with authentication information*/
        corsConfiguration.setAllowCredentials(true);
        /*Allowed client domain name*/
        corsConfiguration.addAllowedOrigin("*");
        /*Client request header to allow server access*/
        corsConfiguration.addAllowedHeader("*");
        /*Method name allowed to access, GET POST, etc*/
        corsConfiguration.addAllowedMethod("*");
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    }
}

6. Login interface

    @PostMapping("/login")
    @ResponseBody
    public ModelMap userLogin(@RequestBody User userform, HttpServletRequest request, HttpServletResponse response, @RequestHeader("language") String language) {
        String status;
        String url = redirectUrl;
        String token1="";
        int code;
        ModelMap result = new ModelMap();
        Subject currentUser = SecurityUtils.getSubject();
        try {
            currentUser.login(token);
            User user = userService.getUserInfo(userform.getUserAccount(), ShiroUtils.generatePwdEncrypt(userform.getUserPassword(), salt));
            if (user != null) {
                try {
                    token1 = TokenUtil.sign(userform);
                    status = "success";
                    code = 200;
                } catch (JedisConnectionException | RedisConnectionFailureException e) {
                    //User login record
                    userLog(user, request.getRemoteUser(), "login_error:" + e.toString());
                    status = "fail";
                    code = 400;
                }
            } else {
                User userTemp = new User();
                userTemp.setUserId("null");
                userTemp.setUserAccount("null");
                userLog(userTemp, request.getRemoteUser(), "login_error:user is not exist!");
                status = "fail";
                code = 401;
            }
        } catch (AuthenticationException e) {
            //User login record
            User userTemp = new User();
            userTemp.setUserId("null");
            userTemp.setUserAccount("null");
            userLog(userTemp, request.getRemoteUser(), "login_error:user is not exist!");
            status = "fail";
            code = 401;
        }
        result.put("status", status);
        result.put("code", code);
        result.put("token",token1);
        result.put("url", "null");
        return result;
    }

3, Front end (vue)

1. Create a store folder in the src directory

Create a new index in the store folder js:

  [SET_TOKEN]: (state, token: string) => {
    state.token = token;
    ls.set(STORAGE_TOKEN_KEY, token);
  },

2. Add routing guard

import router from '@/router';
import store from '@/store';
import localStorage from '@/utils/local-storage';
import { allowList, loginRoutePath } from '../define-meta';
import { STORAGE_TOKEN_KEY } from '@/store/mutation-type';
// eslint-disable-next-line
import { GENERATE_ROUTES, GENERATE_ROUTES_DYNAMIC, GET_INFO } from '@/store/modules/user/actions';

router.beforeEach(async to => {
  const userToken = localStorage.get(STORAGE_TOKEN_KEY);
  // token check
  if (!userToken) {
    // Whitelist routing list check
    if (allowList.includes(to.name as string)) {
      return true;
    }
    if (to.fullPath !== loginRoutePath) {
      // If you are not logged in, go to the login page
      return {
        path: loginRoutePath,
        replace: true,
      };
    }
    return to;
  }

  // check login user.role is null
  if (store.getters['user/allowRouters'] && store.getters['user/allowRouters'].length > 0) {
    return true;
  } else {
    const info = await store.dispatch(`user/${GET_INFO}`);
    // Use the permission information of the current user to generate a routing table with corresponding permissions
    const allowRouters = await store.dispatch(`user/${GENERATE_ROUTES}`, info);
    if (allowRouters) {
      return { ...to, replace: true };
    }
    return false;
  }
});

router.afterEach(() => {});

3. Add interceptor

// request interceptor 
const requestHandler = (
  config: AxiosRequestConfig,
): AxiosRequestConfig | Promise<AxiosRequestConfig> => {
  const savedToken = localStorage.get(STORAGE_TOKEN_KEY);
  // If the token exists
  // Let each request carry a custom token. Please modify it according to the actual situation
  if (savedToken) {
    config.headers[REQUEST_TOKEN_KEY] = savedToken;
  }
  config.headers[language] = 'cn';
  return config;
};

4. Login test

  [LOGIN]({ commit }, info: LoginParams) {
    return new Promise((resolve, reject) => {
      // call ajax
      postAccountLogin(info)
        .then(res => {
          commit(SET_TOKEN, res.token);
          resolve(res);
        })
        .catch(error => {
          reject(error);
        });
    });
  },

There is a lot of code, and the writing is miscellaneous. I'm afraid I'll forget. The cookie used in the previous project has been modified on this basis. Thank you for your help.

Link: VUE SPRINGBOOT implements TOKEN login and access verification.
Link: User login and token authentication in Vue project.

Topics: Java Shiro Vue filter token