SpringBoot中使用JWT進行加密並做訪問攔截

      在前後端分離開發中,用戶登陸成功後一般會生成token,在前後端進行攜帶驗證。使用jwt加密的方式,token將會被前端放置在請求頭中(當然作爲請求參數傳遞也是允許的,看前端開發者的心情。),後端通過request.getHeader("token")來獲取到token並進行驗證。在需要用戶登錄後才能訪問的接口上加入自定義的註解,當用戶發起請求會被攔截器攔截進行驗證,驗證通過則放行,驗證失敗則返回相應的信息給前臺,提示用戶先進行登錄才允許訪問。類似於sso單點登錄的校驗。最近的APP開發中剛好有這樣的需求,特意做一個總結【高手可以直接忽略,不喜勿噴。同時也歡迎廣大讀者朋友提出你們的意見】

1.maven依賴【使用jwt加密需要導入的依賴】

        <dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.9.1</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->  
		<dependency>  
		    <groupId>com.alibaba</groupId>  
		    <artifactId>fastjson</artifactId>  
		    <version>1.2.41</version>  
		</dependency>  

2.工具類

public class JwtUtil {

    public static String encode(String key, Map<String, Object> param, String salt) {
        if (salt != null) {
            key += salt;
        }
        JwtBuilder jwtBuilder = Jwts.builder().signWith(SignatureAlgorithm.HS256, key);

        jwtBuilder = jwtBuilder.setClaims(param);

        String token = jwtBuilder.compact();
        return token;

    }


    public static Map<String, Object> decode(String token, String key, String salt) {
        Claims claims = null;
        if (salt != null) {
            key += salt;
        }
        try {
            claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
        } catch (JwtException e) {
            return null;
        }
        return claims;
    }
}

HttpclientUtil 模擬瀏覽器發送請求的工具類

import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class HttpclientUtil {

    public static String doGet(String url) {
        // 創建Httpclient對象
        CloseableHttpClient httpclient = HttpClients.createDefault();
        // 創建http GET請求
        HttpGet httpGet = new HttpGet(url);
        CloseableHttpResponse response = null;
        try {
            // 執行請求
            response = httpclient.execute(httpGet);
            // 判斷返回狀態是否爲200
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                HttpEntity entity = response.getEntity();
                String result = EntityUtils.toString(entity, "UTF-8");
                EntityUtils.consume(entity);
                httpclient.close();
                return result;
            }
            httpclient.close();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        return null;
    }


    public static String doPost(String url, Map<String, String> paramMap) {
        // 創建Httpclient對象
        CloseableHttpClient httpclient = HttpClients.createDefault();
        // 創建http Post請求
        HttpPost httpPost = new HttpPost(url);
        CloseableHttpResponse response = null;
        try {
            List<BasicNameValuePair> list = new ArrayList<>();
            for (Map.Entry<String, String> entry : paramMap.entrySet()) {
                list.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
            HttpEntity httpEntity = new UrlEncodedFormEntity(list, "utf-8");

            httpPost.setEntity(httpEntity);
            // 執行請求
            response = httpclient.execute(httpPost);

            // 判斷返回狀態是否爲200
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                HttpEntity entity = response.getEntity();
                String result = EntityUtils.toString(entity, "UTF-8");
                EntityUtils.consume(entity);
                httpclient.close();
                return result;
            }
            httpclient.close();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

        return null;
    }
}

3.在用戶登錄成功後,使用jwt進行加密並傳值到前臺

         用戶登錄驗證部分省略,只展示使用jwt加密,並把信息存儲在redis中的部分。
        ResultMsg 是封裝好的用於前後臺傳值的實體類
            ResultMsg msg = new ResultMsg();
           Map<String, Object> param =new HashMap<>();
			param.put("uuid", user.getUuid());
			param.put("phone", user.getPhone());
			String salt = request.getRemoteAddr();
			String encode = JwtUtil.encode("2020ms", param , salt);
			HashMap<String,Object> map = new HashMap<String,Object>();
			map.put("user", user);
			//========暫時把用戶的登錄有效期設置爲一天====修改====
			redisUtil.hmset(phone, map, 1*24*60*60);
			msg.setCode(200);
			msg.setPublicStr(phone);
			msg.setMsg("用戶登陸成功!!!");
			return msg;

4.驗證token是否有效的方法


	@RequestMapping("/verify")
    @ResponseBody
    public String verify(String token, String currentIp, HttpServletRequest request) {
        System.out.println("開始校驗token數據.........");
        // 通過jwt校驗token真假
        Map<String, Object> map = new HashMap<>();
        Map<String, Object> decode = JwtUtil.decode(token, "2020ms", currentIp);
            try {
                if (decode != null) {
                    Object uuid = decode.get("uuid");
                    Object phone = decode.get("phone");
                    System.out.println("檢驗phone="+phone);
                    System.out.println("檢驗uuid="+uuid);
                    Map<Object, Object> hmget = redisUtil.hmget(token);
                    //通過token取到redis中存儲的數據
                    if(null != hmget && hmget.size()>0) {
                    	User redis_user = (User) hmget.get("user");
                    	//if(null != redis_user) {
                    		map.put("status", "success");
                    		System.out.println("校驗成功.........");
                    		System.err.println("token校驗時的redis_user:"+redis_user);
                    //	}else {
                      //      map.put("status", "fail");
                      //      System.out.println("token校驗失敗.........");
                     //   }
                    }else {
                        map.put("status", "fail");
                        System.out.println("token校驗失敗.........");
                    }

            } else {
                map.put("status", "fail");
                System.out.println("校驗失敗.........");
            }

            }catch (NullPointerException e){
                map.put("status", "fail");
                System.out.println("校驗失敗.........");
           }

        return JSON.toJSONString(map);
    }

5.編寫攔截器

@Component
public class AuthInterceptor extends HandlerInterceptorAdapter {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//            registry.addResourceHandler("/upload/**").addResourceLocations("file:C:/work/temp/temp0/temp1/upload/");
//如果請求爲靜態資源請求時,類型轉換會報錯,類型不對應,所以應再請求時方法請求時再轉換類型
// 如果不是映射到方法直接通過
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        // 攔截代碼
        // 判斷被攔截的請求的訪問的方法的註解(是否時需要攔截的)
        HandlerMethod hm = (HandlerMethod) handler;
        LoginRequired methodAnnotation = hm.getMethodAnnotation(LoginRequired.class);

        StringBuffer url = request.getRequestURL();
        System.out.println("url="+url);

        // 是否攔截
        if (methodAnnotation == null) {
            return true;//沒有註解的都放行
        }

        String token = request.getHeader("token");
        System.out.println("攔截器前臺傳的token=  "+token);

        // 是否必須登錄
        boolean loginSuccess = methodAnnotation.loginSuccess();// 獲得該請求是否必登錄成功
        System.out.println("該請求是否必須登錄成功"+loginSuccess);

        // 調用認證中心進行驗證
        String success = "fail";
        Map<String,String> successMap = new HashMap<>();

        if(StringUtils.isNotBlank(token)){//判斷token是否爲空  是的話不走該方法
//            String ip = request.getHeader("x-forwarded-for");// 通過nginx轉發的客戶端ip
//            if(StringUtils.isBlank(ip)){
//                ip = request.getRemoteAddr();// 從request中獲取ip
//                if(StringUtils.isBlank(ip)){
//                    ip = "127.0.0.1";
//                }
//            }
          //  String ip ="127.0.0.1";
            String ip =request.getRemoteAddr();
            System.err.println("當前訪問的用戶IP爲:"+ip);
            String successJson  = HttpclientUtil.doGet("http://127.0.0.1:88/verify?token=" + token+"&currentIp="+ip);

            successMap = JSON.parseObject(successJson,Map.class);

            success = successMap.get("status");
            System.out.println("返回的校驗後的狀態爲:success= "+success);

        }

        if (loginSuccess) {
            // 必須登錄成功才能使用
            if (!success.equals("success")) {
                System.out.println("IF判斷裏success= "+success);
                //重定向會passport登錄
                StringBuffer requestURL = request.getRequestURL();
                //tologin爲自定義的方法,讓用戶去登錄
                response.sendRedirect("http://127.0.0.1:88/tologin");
                return false;
            }

//            // 需要將token攜帶的用戶信息寫入
//            request.setAttribute("memberId", successMap.get("memberId"));
//            request.setAttribute("nickname", successMap.get("nickname"));


        }
//        else {
            // 沒有登錄也能用,但是必須驗證
//            if (success.equals("success")) {
//                // 需要將token攜帶的用戶信息寫入
//                request.setAttribute("memberId", successMap.get("memberId"));
//                request.setAttribute("nickname", successMap.get("nickname"));
//
//
//
//            }
//        }


        return true;
    }
}

6.自定義註解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)//在方法範圍內生效
@Retention(RetentionPolicy.RUNTIME)//在虛擬機範圍內也生效
public @interface LoginRequired {
    //通過註解的方式來標識具體的方法是否需要通過攔截器
    boolean loginSuccess() default true;

}

7.配置攔截器的攔截路徑,在springboot中它沒有web.xml而我們剛剛自定義的攔截器相當於聲明瞭一個組件,也可以理解爲寫了一個攔截方法,需要配置攔截路徑方能生效,【本人層踩過這個坑,忽略了對攔截器的配置導致攔截器不生效。。。】

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.pf.bindDate.web.interceptor.AuthInterceptor;

@ControllerAdvice//切面類
@Configuration
/**
 * 自定義資源映射
 *通過addResourceHandler添加映射路徑,然後通過addResourceLocations來指定路徑。
 */
public class WebConfig extends WebMvcConfigurerAdapter {
	@Autowired
    AuthInterceptor authInterceptor;
   
    @Value("${file-save-path}")
    private String fileSavePath;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
//指定攔截路徑,類似於web.xml中的配置
        registry.addInterceptor(authInterceptor).addPathPatterns("/**")
                .excludePathPatterns("/error")
                .excludePathPatterns("/pf_dd/uploads/**");
        super.addInterceptors(registry);


    }

    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        /**
         * 配置資源映射
         * 意思是:如果訪問的資源路徑是以“/images/”開頭的,
         * 就給我映射到本機的“E:/images/”這個文件夾內,去找你要的資源
         * 注意:E:/images/ 後面的 “/”一定要帶上
         */        
    	
        registry.addResourceHandler("/pf_dd/uploads/**").addResourceLocations("file:"+fileSavePath);
        registry.addResourceHandler("/statics/**").addResourceLocations("classpath:/statics/");
        // 解決 SWAGGER 404報錯
        registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
    }

}

8.接下來就可以在Controller中需要攔截的方法上面加@LoginRequired註解,達到我們對必須登錄才能訪問的接口的校驗。

   同樣的需求可以有多種解決辦法,我們要做的就是儘量寫出安全可靠高效的程序。革命尚未成功,同志仍需努力。加油!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章