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注解,达到我们对必须登录才能访问的接口的校验。

   同样的需求可以有多种解决办法,我们要做的就是尽量写出安全可靠高效的程序。革命尚未成功,同志仍需努力。加油!!!

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