Spring boot security jwt 前后台分离与遇到的问题

 

 

最近自己在练习spring boot ,发现真的很好用,登陆是必不可少的,自然而然的就用到了security,然后发现了jwt,就试着做了一下,确实是搞出来了,但是遇到了一些问题。

整合Spring boot security jwt我参考的是Spring Boot中使用使用Spring Security和JWT

大家自己去看看,我只是稍作修改,原理流程和配置都是用的这个。

但是文章都有了我为什么还要写这个呢?

因为我发现了bug啊!!!

所以不要点了我的发的文章链接就把我给关了!!!

至少先看完我发现了什么bug!!!

        首先说一下我做的是前后台分离的项目,网上我找了很多的文章,确实是可以达到整合的目的,但是你确定整合了就能用?

        网上的文章大部分都是使用postman工具进行测试是否整合完成,他们确定返回值正常就代表整合完成了,也就是说在理想环境下是没问题的,但是我们在开发的时候不确定的因素太多了,我是实实在在的用项目进行测试。

废话说太多了,我们开始聊bug。

       首先spring boot 我用的2.x版本,在整合完成之后我确实是看到了返回值正常,token什么的都有,但是仅限于登录,之后请求数据就直接报错了,因为我项目都搞好了,所以我就不改回去把报错信息给大家看(其实怕出问题又改出一堆问题):

1.跨域的问题

        这个时候可能大家会问一个问题,登录都成功了怎么会存在跨域的问题?你是没有配置跨域访问吧?mdzz,这都不会?。。。

        首先,跨域访问我确实配置了,跨域配置如下:

       但是为什么会出现跨域不能访问的问题呢?带着问题找答案,网上确实没有什么文章说这个的,那就只能自己找了

       我就观察前台发送的网络请求,发现有一个OPTIONS的请求,请求地址和我需要请求数据的地址是一样的,但是没有我设置的请求头,好像是浏览器自动发送的,返回值是200,但是就是报错,说跨域不能访问,找了一些文章,说是测试服务器是否支持该类型请求。

        那么我明明配置了跨域为什么还是会出测试出我不能跨域呢?来看看我推荐的文章里面写的东西

        我的测试请求中是没有携带token的,也就是说它会被拦截,最后的结果就是用户没有登录,然后就跳到这货写的拦截器里面去了,他的拦截器也是厉害,就写一个跨域头外加一个错误码,我试过这样的配置,直接是不能跨域,看看我上面贴出的跨域配置写了那么多,他就写一个,有什么办法,改喽。

 http.exceptionHandling().authenticationEntryPoint(getEntryPointUnauthorizedHandler()).accessDeniedHandler(getRestAccessDeniedHandler());
//上面这一段是设置两个权限不足的地方

//401错误
class EntryPointUnauthorizedHandler implements AuthenticationEntryPoint {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
            jwtAuthenticationTokenFilter.setCorsResponse(request, response);//这个是设置返回头的地方,我单独写到了jwtFilter里面去了,因为还有其他用处
            response.setStatus(401);
        }
    }

//403错误
    class RestAccessDeniedHandler implements AccessDeniedHandler {
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) {
            jwtAuthenticationTokenFilter.setCorsResponse(request, response);//这个是设置返回头的地方,我单独写到了JwtAuthenticationTokenFilter里面去了,因为还有其他用处
            response.setStatus(403);
        }
    }

//实例化成bean就可以直接用了,不用像他那样还要新建两个类,我写了两个内部类就行了
    @Bean
    public EntryPointUnauthorizedHandler getEntryPointUnauthorizedHandler() {
        return new EntryPointUnauthorizedHandler();
    }

    @Bean
    public RestAccessDeniedHandler getRestAccessDeniedHandler() {
        return new RestAccessDeniedHandler();
    }

JwtAuthenticationTokenFilter中的请求头配置 ,和上面自己之前写的配置同步就行

    public void setCorsResponse(HttpServletRequest request, HttpServletResponse response) {
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin"));//跨域资源请求,*代表任何请求都可以访问
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");//支持的请求方式
        response.setHeader("Access-Control-Max-Age", "3600");//跨域缓存实践,这里设置的是一个小时
        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,authorization");//支持的请求头,* 代表所有
        response.setHeader("Access-Control-Allow-Credentials", "true");
    }

 这一下跨域的问题算是解决了,但是新问题又来了

2.验证不通过

        刚才说到了浏览器高级跨域会发送一个测试请求OPTIONS来测试服务器是否支持跨域,但是这个是不会返回数据的,它只是测试,但是还是被security拦截了,然后用推文里面的方法返回的不是200码,当然我觉得就算用其他的也会有这个问题,毕竟这个请求确实没有通过验证啊,没通过验证我肯定不能给你正确的返回的,不然我还需要验证干嘛。

        这个时候我又上网查了一堆资料,发现没有能解决这个问题的文章,很无奈的又只能自己解决了。

        怎么解决呢,其实很简单,只要让这个请求不参与验证就可以了呗,有了思路又开始查资料,发现可以写一个过滤器,写一个CorsFilter就可以了,话不多说贴代码

package com.zh.lore.list.file.config.app;

import org.springframework.context.annotation.Configuration;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Created by zenghang on 2019/2/12.
 */
//@Configuration
public class CorsFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse res = (HttpServletResponse) response;
        HttpServletRequest req = (HttpServletRequest) request;

        res.setHeader("Access-Control-Allow-Origin", req.getHeader("origin"));
        res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        res.setHeader("Access-Control-Max-Age", "3600");
        res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,authorization");
        res.setHeader("Access-Control-Allow-Credentials", "true");

        //由于使用前后分离的方式,发现每次请求之前都会发送一个OPTIONS来测试服务器是否支持跨域,但是又被security jwt拦截,所以这里发现这个请求之后直接返回
        if (req.getMethod().equals("OPTIONS")) {
            res.setStatus(200);
            return;
        }

        chain.doFilter(req, res);
    }

    @Override
    public void destroy() {

    }
}

        这样写确实可以修复这个bug,但是我这个人太懒,单独为这一个功能写个过滤器太麻烦了吧,而且每个请求都会进入这个过滤器,也有点浪费,所以你们可以看到我上面的@configuration被屏蔽掉了,这个类我也会弃用的。

        我在调试的时候发现请求进来的时候优先进入的不是这个过滤器,而是JwtAuthenticationTokenFilter这个过滤器,同时里面都有一段chain.doFilter(req, res);这个代码,所以我果断的修改JwtAuthenticationTokenFilter,贴出我的JwtAuthenticationTokenFilter:

package com.zh.lore.list.file.config.filter;

import com.zh.lore.list.file.entity.User;
import com.zh.lore.list.file.service.UserService;
import com.zh.lore.list.file.utils.JwtUtil.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Created by zenghang on 2019/1/17.
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private UserService userDetailsService;
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    public JwtAuthenticationTokenFilter(UserService userDetailsService, JwtTokenUtil jwtTokenUtil) {
        this.userDetailsService = userDetailsService;
        this.jwtTokenUtil = jwtTokenUtil;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
        String tokenHead = "Bearer ";
        if (authHeader != null && authHeader.startsWith(tokenHead)) {
            String authToken = authHeader.substring(tokenHead.length());
            String username = jwtTokenUtil.getUsernameFromToken(authToken);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                User userDetails = (User) this.userDetailsService.loadUserByUsername(username);
                if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        } else {
            setCorsResponse(request, response);

            //由于使用前后分离的方式,发现每次请求之前都会发送一个OPTIONS来测试服务器是否支持跨域,但是又被security jwt拦截,所以这里发现这个请求之后直接返回
            if (request.getMethod().equals("OPTIONS")) {
                response.setStatus(200);
                return;
            }
        }

        chain.doFilter(request, response);
    }

    public void setCorsResponse(HttpServletRequest request, HttpServletResponse response) {
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin"));
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,authorization");
        response.setHeader("Access-Control-Allow-Credentials", "true");
    }

}

       现在一切正常了,但是其实还有一个问题,也是属于跨域的问题,不过按照我上面写的进行修改,这个问题也就不存在了,但是我还是得提一下

3.跨域

       跨域的时候要设置资源请求头,有哪些请求头可以访问,如果有不在设置里面的请求头出现那么这个请求也是不能跨域的,我们这个就有一个authorization这样的请求头,最开始我是没有设置的,也是一直不能跨域访问,在jwt的过滤器中可以看到jwt 的是会获取这个头信息的,也就是说这个头是我们自定义的,需要在跨域中进行配置,当然这只是一个小细节,稍微注意下就行了。

 

 

好了,自此我的问题描述完毕,都看到这里了我想可能会对你有帮助吧,麻烦点个赞再走呗~~~

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值