当前位置: 首页 > article >正文

IntelliJ+SpringBoot项目实战(十八)--在SpringBoot中整合SpringSecurity和JWT(下C)

九、实现JWT认证过滤器        

        在上节中实现了SpringSecurity的权限控制,本节介绍JWT登录相关的实现。

        9.1 实现JwtAuthenticationFilter

              首先实现JwtAuthenticationFilter,此过滤器的作用是,检查头部的Authorization中是否有

              accessToken,如果没有,按照传统的登录方式进行,如果有accessToken,则从accessToken中解析出登录账号,然后通过CommUser sysUser = sysUserService.selectUserByLoginId(username);从数据库中读取用户信息,并封装到SpringSecurity上下文中实现自动登录:

    UsernamePasswordAuthenticationToken token = new 
    UsernamePasswordAuthenticationToken(username, null, 
    userDetailService.getUserAuthority(sysUser.getLoginId()));
    SecurityContextHolder.getContext().setAuthentication(token);

    chain.doFilter(request, response);

下面是JwtAuthenticationFilter完整代码:

package org.openjweb.sys.filter;

import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import lombok.extern.slf4j.Slf4j;
import org.openjweb.core.entity.CommUser;
import org.openjweb.core.service.CommUserService;
import org.openjweb.core.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j

public class JwtAuthenticationFilter extends BasicAuthenticationFilter {

    @Autowired
    JwtUtil jwtUtils;

    @Autowired
    CommUserService userDetailService;

    @Autowired
    CommUserService sysUserService;

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String jwt = null;
        if(jwtUtils==null){
            log.info("jwtUtils is null。。。。。。。。。。");
        }
        else{
            log.info("jwt utils is not null,jwtutils.getHeader:");//非空
            log.info(jwtUtils.getHeader());//空
            String header = jwtUtils.getHeader();//
            if(header!=null) {
                log.info("jwt header:::::"+header );
                jwt = request.getHeader(header);
            }

        }

        // 这里如果没有jwt,继续往后走,因为后面还有鉴权管理器等去判断是否拥有身份凭证,所以是可以放行的
        // 没有jwt相当于匿名访问,若有一些接口是需要权限的,则不能访问这些接口
        if (StrUtil.isBlankOrUndefined(jwt)) {
            log.info("没有jwt信息,继续filter........");
            chain.doFilter(request, response);
            return;
        }
        else{
            log.info("有jwt..........");
        }

        Claims claim = jwtUtils.getClaimsByToken(jwt);
        if (claim == null) {
            throw new JwtException("token 异常");
        }
        if (jwtUtils.isTokenExpired(claim)) {
            throw new JwtException("token 已过期");
        }
        String username = claim.getSubject();
        // 获取用户的权限等信息

        CommUser sysUser = sysUserService.selectUserByLoginId(username);

        // 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, userDetailService.getUserAuthority(sysUser.getLoginId()));
        SecurityContextHolder.getContext().setAuthentication(token);

        chain.doFilter(request, response);

    }
}

        然后在WebSecurity.java中配置此过滤器:

    @Bean
    JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
        JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager());
        return jwtAuthenticationFilter;
    }

       然后需要将这个过滤器已加到配置里:

        

         不过实际测试时,没有增加上面红框的代码,过滤器也生效了,可能这样加可以对过滤器的执行顺序进行排序。

        然后我们启动SpringBoot,仍访问http://localhost:8001/auth/test。控制台显示:

        说明过滤器已经被调用。

十、登录成功处理

        接下来增加一个登录成功处理类,当登录成功后,将生成的accesstoken设置到header中:

package org.openjweb.sys.handler;
 
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.openjweb.common.response.ResponseResult;
import org.openjweb.core.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Component
@Slf4j
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    JwtUtil jwtUtils;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();

        log.info("success ,add jwt accesstoken to header.........");


        // 生成JWT,并放置到请求头中
        String jwt = jwtUtils.generateToken(authentication.getName());
        log.info("jwtUtils.getHeader():");
        log.info(jwtUtils.getHeader());
        log.info(jwt);
        httpServletResponse.setHeader(jwtUtils.getHeader(), jwt);
        ResponseResult result = ResponseResult.okResult("SuccessLogin");//登录成功
        String json = JSONUtil.toJsonStr(result);
        outputStream.write(json.getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

        然后在WebSecurityConfig.java中,增加这个Handler:

    @Autowired
    LoginSuccessHandler loginSuccessHandler;

    启动SpringBoot,还是访问localhost:8001/api/auth/test,登录成功的界面:

    在控制台中可以看到生成的accessToken:

十、登录失败处理

        增加一个登录失败处理类LoginFailureHandler:

package org.openjweb.sys.handler;


import cn.hutool.json.JSONUtil;
import org.openjweb.common.response.ResponseResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                                        AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
        String errorMessage = "用户名或密码错误";
        ResponseResult result = ResponseResult.errorResult(-1,errorMessage);
        String json = JSONUtil.toJsonStr(result);

         
        outputStream.write(json.getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

       然后在WebSecureConfig中增加组件:

    @Autowired
    LoginSuccessHandler loginSuccessHandler;

        

启动SpringBoot,访问localhost:8001/api/auth/test,输入错误的密码,返回的界面:

十一、异常处理

        当未登录的时候访问受限资源或权限不足,可进行下面的处理。未登录访问受限资源,使用JwtAuthenticationEntryPoint来处理,当权限不足时,使用JwtAccessDeniedHandler处理,下面是实现代码:

package org.openjweb.sys.handler;

import cn.hutool.json.JSONUtil;
import org.openjweb.common.response.ResponseResult;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                       AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
        //错误码后面在常量类中补充
        ResponseResult result = ResponseResult.errorResult(-2,"权限不足");//权限不足
        String json = JSONUtil.toJsonStr(result);

        outputStream.write(json.getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}
package org.openjweb.sys.entry;

import cn.hutool.json.JSONUtil;
import org.openjweb.common.response.ResponseResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {


    //认证失败的处理
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {

        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();
        ResponseResult result = ResponseResult.errorResult(-3,"请先登录");//权限不足
        String json = JSONUtil.toJsonStr(result);
        outputStream.write(json.getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

        在WebSecurityConfig中配置:

    @Autowired
    JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
    JwtAccessDeniedHandler jwtAccessDeniedHandler;

增加异常处理:

十二、退出登录成功处理

        退出登录成功处理使用JWTLogoutSuccessHandler类:

package org.openjweb.sys.handler;

import cn.hutool.json.JSONUtil;
import org.openjweb.common.response.ResponseResult;
import org.openjweb.core.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Component
public class JWTLogoutSuccessHandler implements LogoutSuccessHandler {

    @Autowired
    JwtUtil jwtUtils;

    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                                Authentication authentication) throws IOException, ServletException {

        if (authentication != null) {
            new SecurityContextLogoutHandler().logout(httpServletRequest, httpServletResponse, authentication);
        }

        httpServletResponse.setContentType("application/json;charset=UTF-8");
        ServletOutputStream outputStream = httpServletResponse.getOutputStream();

        httpServletResponse.setHeader(jwtUtils.getHeader(), "");

        ResponseResult result = ResponseResult.okResult("注销成功!");
        String json = JSONUtil.toJsonStr(result);

        outputStream.write(JSONUtil.toJsonStr(result).getBytes(StandardCharsets.UTF_8));
        outputStream.flush();
        outputStream.close();
    }
}

        在WebSecurityConfig中配置:

    @Autowired
    JWTLogoutSuccessHandler jwtLogoutSuccessHandler;

顺便增加了session禁用的配置。注销成功处理,注销成功可以访问http://localhost:8001/logout

界面显示:{"msg":"操作成功","code":0,"data":"注销成功!"}

注:实测登录时,如果增加了.authenticationEntryPoint(jwtAuthenticationEntryPoint)会导致下次登录不能访问/login页面,所以应去掉这行代码。

        写到这里,SpringSecurity和JWT的整合已经完全实现了。但是还有一个问题,SpringSecurity的登录界面自定义?而且在前后端分离的项目中,登录是VUE端通过接口调用登录的,而不是走/login界面的登录。下一篇文章介绍SpringBoot的前后端分离模式的登录方式。

本文完整代码见Github: GitHub - openjweb/cloud at masterOpenJWeb is a java bases low code platform. Contribute to openjweb/cloud development by creating an account on GitHub.icon-default.png?t=O83Ahttps://github.com/openjweb/cloud/tree/master


http://www.kler.cn/a/419225.html

相关文章:

  • Vue进阶之单组件开发与组件通信
  • WRF-Chem模式安装、环境配置、原理、调试、运行方法;数据准备及相关参数设置方法
  • 「Qt Widget中文示例指南」如何为窗口实现流程布局?(二)
  • S4 UPA of AA :新资产会计概览
  • Doge东哥wordpress主题
  • 51c视觉~YOLO~合集4
  • ChatGPT/AI辅助网络安全运营之-数据解压缩
  • uniapp如何发起网络请求uni.request
  • 选择排序之大根堆
  • ubuntu20.04更换安装高版本CUDA以及多个CUDA版本管理
  • 【Java基础面试题003】Java的JIT | AOT是什么?
  • 【数据可视化入门】Python散点图全攻略:Matplotlib、Seaborn、Pyecharts实战代码大公开!
  • leetcode 3232. 判断是否可以赢得数字游戏 简单
  • 计算机网络安全 —— 非对称加密算法 RSA 和数字签名
  • springboot363高校竞赛管理系统(论文+源码)_kaic
  • 服务熔断-熔断器设计
  • 【JavaEE】JavaEE、web 开发、框架(Spring) 、Maven
  • SAP-ABAP开发-第二代增强示例
  • Day1——GitHub项目共同开发
  • Vue进阶之单组件开发与组件通信
  • Unity中实现A*寻路算法参考
  • Scala 的知识点
  • Linux中pcap_loop()函数
  • 算法的复杂度
  • LeetCode:19.删除链表倒数第N个节点
  • 解决jupyter notebook 新建或打开.ipynb 报500 : Internal Server Error(涉及jinja2兼容性问题)