美文网首页Java
SpringSecurity整合JWT

SpringSecurity整合JWT

作者: TZX_0710 | 来源:发表于2019-09-18 17:09 被阅读0次

    SpringSecurity前面讲解的一些配置都是基于前后端都是一起的,那么当分开了的时候就会出现一系列问题,跨域之类的问题随之而出 所以出现了jwt帮助我们完成SpringSecurity前后端分离权限控制
    jwt (json web Token)它是基于RFC 7519开放标准用于双方安全展示信息的一种方式。通俗说就是是用于服务端和客户端相互交换信息的一种凭证。如果用过aouth2的小伙伴本对这个应该不陌生Token。
    在传统模式当中我们的认证流程是
    用户登录->服务端生成session->cookie ->服务端写代session->查找用户->findOK

    1.服务端需一定资源保存session信息,用户多时资源消耗较大
    扩展性不好,当我们的服务端需要集群时,
    2.因session保存在服务端,此时无法定位session,造成登录失效
    3.跨域问题,当我们访问A网站时,此时不想再登录就能够访问关联网站B。(传统解决办法:写入持久层,A,B同时访问)
    所以现在可以采用的解决办法,不需要服务端去保存session,
    用户登录--->服务端生成凭证->携带凭证带客户端——>客户端保存凭证->每次客户端请求都携代凭证,客户端通过则验证成功,调用API获取信息。所以就有了jwt的诞生

    jwt的组成部分

    1. header(头),保存算法,类型
    2. payload(负载),用户的信息,如id,用户名等等
    3. signature(签名),将生成的token编码(加密)
      他们之间用 "."号隔开。

    新建springboot项目
    引入pom

          <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.9.1</version>
            </dependency>
    

    编写yml文件

    server:
      port: 8080
    
    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: root
        url:  jdbc:mysql://localhost:3306/security_db?useSSL=false&serverTimezone=UTC
    
      jpa:
        show-sql: true
        hibernate:
          ddl-auto: update
        properties:
          hibernate:
            format_sql: true
    
    logging:
      level:
        org.springframework.*: debug
    

    参考之前文章,编写实体和repository这里就不放代码了 直接放截图


    image.png

    因为是前后端分离的所有封装了一个专们响应前台数据的实体类

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class ResponseDto<V> {
    
        
        private int code;
    
        private String msg;
    
        private V data;
    }
    //编写securityConfig
    
    
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        //添加异常
        @Resource
        private TokenExceptionHandler tokenExceptionHandler;
    
        @Resource
        private AccessDeniedHandler accessDeniedHandler;
    
        @Resource
        private JwtTokenFilter jwtTokenFilter;
    //获取Token的接口不必拦截
        @Override
        public void configure(WebSecurity web) throws Exception {
    
            web.ignoring().antMatchers( HttpMethod.GET,"/token" );
             }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //前后端分离所以不要考虑csrf可以禁用掉
            http.csrf().disable()
                    //添加异常处理
                    .exceptionHandling().authenticationEntryPoint( tokenExceptionHandler )
    
                    .accessDeniedHandler( accessDeniedHandler )
                    //
                    .and().sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS )
                    //拦截所有请求
                    .and().authorizeRequests().anyRequest().authenticated();
            //定义filter addFilterAt  用于filter替换
            http.addFilterAt(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
            //super.configure( http );
        }
    }
    
    //对没有Token 的请求进行拦截  编写handler处理
    
    //验证没有token异常
    @Component
    public class TokenExceptionHandler implements AuthenticationEntryPoint {
    
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    
            // 直接返回 json错误
            ResponseDto <Object> result = new ResponseDto<>();
            //20,标识没有token
            result.setCode(20);
            result.setMsg("请求无效,没有有效token");
    
            ObjectMapper objectMapper = new ObjectMapper();
    
            response.getWriter().write(objectMapper.writeValueAsString(result));
        }
    }
    
    //访问被拒绝处理
    @Component
    public class AccessDeniedHandler implements org.springframework.security.web.access.AccessDeniedHandler {
    
    
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
            // 返回我们的自定义json
            ObjectMapper objectMapper = new ObjectMapper();
            ResponseDto <Object> result = new ResponseDto<>();
            //50,标识有token,但是该用户没有权限
            result.setCode(50);
            result.setMsg("请求无效,没有有效token");
            response.getWriter().write(objectMapper.writeValueAsString(result)); // 返回我们的自定义json
        }
    }
    
    //自定义Filter
    @Component
    public class JwtTokenFilter extends OncePerRequestFilter {
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
             request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
            String token = request.getHeader("token");
    
            //获取token,并且解析token,如果解析成功,则放入 SecurityContext
            if (token != null) {
                try {
                    AuthUser authUser = JwtUtil.parseToken(token);
                    //todo: 如果此处不放心解析出来的 authuser,可以再从数据库查一次,验证用户身份:
    
                    //解析成功
                    if (SecurityContextHolder.getContext().getAuthentication() == null) {
                        //我们依然使用原来filter中的token对象
                        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(authUser, null, authUser.getAuthorities());
    
                        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                    }
                } catch (Exception e) {
                    logger.info("解析失败,可能是伪造的或者该token已经失效了(我们设置失效5分钟)。");
                }
            }
    
            filterChain.doFilter(request, response);
        }
    }
    

    controller代码

    @RestController
    public class UserController {
    
    
        @Resource
        private UserRepository userRepository;
        @Resource
        private RoleRepository roleRepository;
    
        @GetMapping("/token")
        public ResponseDto login(String username, String password) {
            User user = userRepository.findByUsername(username);
    
            if (user == null || !user.getPassword().equals(password)) {
                ResponseDto <Object> result = new ResponseDto <>();
                result.setCode(10);
                result.setMsg("用户名或密码错误");
                return result;
            }
    
            ResponseDto <Object> success = new ResponseDto <>();
            //用户名密码正确,生成token给客户端
            success.setCode(0);
            List <Role> roles = Collections.singletonList(roleRepository.findById(user.getId()).get());
            success.setData( JwtUtil.generateToken(username, roles));
    
            return success;
        }
    }
    
    @RestController
    @RequestMapping
    public class PermissionController {
        @GetMapping("/permission")
        public ResponseDto loginTest(@AuthenticationPrincipal AuthUser authUser) {
            ResponseDto<String> resultVO = new ResponseDto<>();
            resultVO.setCode(0);
    
            resultVO.setData("你成功访问了该api,这代表你已经登录,你是: " + authUser);
            return resultVO;
        }
    
        @GetMapping("/loginUser")
        @PreAuthorize("hasRole('user')")
        public ResponseDto loginTest() {
            ResponseDto<String> resultVO = new ResponseDto<>();
            resultVO.setCode(0);
    
            resultVO.setData("你成功访问了需要有 user 角色的api。");
            return resultVO;
        }
    }
    

    采用postman进行测试 直接放图


    image.png image.png

    带入token在header里面去访问 不需要角色的接口


    image.png
    image.png

    新建一个没有该角色的用户去访问


    image.png

    总结:jwt+security整合流程

    1. 引入jwt security的pom文件
    2. 创建实体 实现userDetail 和security交互的实体类
    3. 自定义需要处理的异常 访问被拒绝的异常等、看个人所需
      4.编写filter 自定义filter 去验证token是否通过 ,以及保证失效token不会进入接口
    4. 编写securityconfig 配置websecurity 让获取token接口绕过security 编写httpsecurity设置设置对web端所有的都进行拦截 都需要经过验证才行,添加异常处理
      添加对filter替换。替换掉UsernamePasswordAuthenticationFilter 该filter默认情况响应的是/login

    相关文章

      网友评论

        本文标题:SpringSecurity整合JWT

        本文链接:https://www.haomeiwen.com/subject/edkkuctx.html