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

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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot是一个用于创建独立的、基于Spring的应用程序的框架,而Spring Security是Spring提供的一个强大的安全框架,用于保护应用程序的安全性。JWT(JSON Web Token)是一种用于身份验证和授权的开放标准。 下面是Spring Boot整合Spring Security和JWT的代码实现步骤: 1. 添加依赖:在`pom.xml`文件添加Spring Security和JWT的依赖。 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 2. 创建配置类:创建一个配置类,用于配置Spring Security和JWT相关的配置。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Autowired private JwtRequestFilter jwtRequestFilter; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { // 配置用户认证逻辑 auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { // 配置密码加密方式 return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { // 配置请求拦截规则 httpSecurity.csrf().disable() .authorizeRequests().antMatchers("/authenticate").permitAll() .anyRequest().authenticated().and() .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 添加JWT过滤器 httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); } } ``` 3. 创建认证逻辑:创建一个实现了`UserDetailsService`接口的类,用于处理用户认证逻辑。 ```java @Service public class JwtUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 根据用户名查询用户信息,并返回UserDetails对象 // ... } } ``` 4. 创建JWT工具类:创建一个JWT工具类,用于生成和解析JWT。 ```java @Component public class JwtTokenUtil { private static final String SECRET_KEY = "your-secret-key"; private static final long EXPIRATION_TIME = 864_000_000; // 10 days public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } public String extractUsername(String token) { return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getSubject(); } public boolean validateToken(String token, UserDetails userDetails) { String username = extractUsername(token); return username.equals(userDetails.getUsername()) && !isTokenExpired(token); } private boolean isTokenExpired(String token) { Date expirationDate = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody().getExpiration(); return expirationDate.before(new Date()); } } ``` 5. 创建认证控制器:创建一个控制器类,用于处理用户认证请求。 ```java @RestController public class AuthenticationController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtUserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @PostMapping("/authenticate") public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()) ); } catch (BadCredentialsException e) { throw new Exception("Incorrect username or password", e); } final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername()); final String token = jwtTokenUtil.generateToken(userDetails); return ResponseEntity.ok(new AuthenticationResponse(token)); } } ``` 以上是Spring Boot整合Spring Security和JWT的代码实现步骤。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值