【SpringSecurity】十一、SpringSecurity集成JWT实现token的方法与校验

news2025/1/17 14:02:52

文章目录

  • 1、依赖与配置
  • 2、JWT工具类
  • 3、认证成功处理器
  • 4、创建JWT过滤器
  • 5、安全配置类

1、依赖与配置

添加JWT的maven依赖:

<!-- 添加jwt的依赖 -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.11.0</version>
</dependency>

application.yaml中配置密钥的值,方便代码中引用和后续更改:

jwt:
  secretKey: mykey

2、JWT工具类

这里的命名改为JWTService好点,Utils命名似乎偏静态方法一点。

@Component
@Slf4j
public class JwtUtils {
    //算法密钥
    @Value("${jwt.secretKey}")
    private String jwtSecretKey;

    /**
     * 创建jwt
     *
     * @param userInfo 用户信息
     * @param authList 用户权限列表
     * 根据登录用户的数据库信息和权限信息,加上服务端密钥,创建token
     * @return 返回jwt(JSON WEB TOKEN)
     */
    public String createToken(String userInfo, List<String> authList) {
        //创建时间
        Date currentTime = new Date();
        //过期时间,5分钟后过期
        Date expireTime = new Date(currentTime.getTime() + (1000 * 60 * 5));
        //jwt的header信息
        Map<String, Object> headerClaims = new HashMap<>();
        headerClaims.put("type", "JWT");
        headerClaims.put("alg", "HS256");
        //创建jwt
        return JWT.create()
                .withHeader(headerClaims) // 头部信息
                .withIssuedAt(currentTime) //已注册声明:签发日期,发行日期
                .withExpiresAt(expireTime) //已注册声明 过期时间
                .withIssuer("llg")  //已注册声明,签发人
                .withClaim("userInfo", userInfo) //私有声明,可以自己定义
                .withClaim("authList", authList) //私有声明,可以自定义
                .sign(Algorithm.HMAC256(jwtSecretKey)); // 签名,使用HS256算法签名,并使用密钥
        //HS256是一种对称算法,这意味着只有一个密钥,在双方之间共享。 使用相同的密钥生成签名并对其进行验证。 应特别注意钥匙是否保密。
    }

    /**
     * 验证jwt的签名,简称验签
     * @param token 需要验签的jwt
     * @return 验签结果
     */
    public boolean verifyToken(String token) {
        //获取验签类对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();
        try {
            //验签,如果不报错,则说明jwt是合法的,而且也没有过期
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            return true;
        } catch (JWTVerificationException e) {
            //如果报错说明jwt 为非法的,或者已过期(已过期也属于非法的)
            log.error("验签失败:{}", token);
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 从token中获取用户信息
     * 这个userInfo是创建token时我自己塞进去的
     * @param token jwt
     * @return 用户信息
     */
    public String getUserInfo(String token) {
        //创建jwt验签对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();
        try {
            //验签(主要为了同时的获取解析的结果)
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            //获取payload中userInfo的值,并返回
            return decodedJWT.getClaim("userInfo").asString();
        } catch (JWTVerificationException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取用户权限
     *
     * @param token
     * @return
     */
    public List<String> getUserAuth(String token) {
        //创建jwt验签对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();
        try {
            //验签(主要为了同时的获取解析的结果)
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            //获取payload中的自定义数据authList(权限列表),并返回
            return decodedJWT.getClaim("authList").asList(String.class);
        } catch (JWTVerificationException e) {
            e.printStackTrace();
        }
        return null;
    }

}

再贴一下下统一结果类的定义:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class HttpResult implements Serializable {
    private Integer code; //响应码
    private String msg; //响应消息
    private Object data; //响应对象
}

下面是安全用户类,用于在数据库的用户对象类SysUser和返给框架的官方对象类UserDetails之间做过渡转换。UserDetails <====> SecurityUser <====> SysUser

@Setter
public class SecurityUser implements UserDetails {

    private  final SysUser sysUser;

	private List<SimpleGrantedAuthority> simpleGrantedAuthorities;

    public SecurityUser(SysUser sysUser) {
        this.sysUser=sysUser;
    }
	public SysUser getSysUser() {
    return sysUser;
}


    @Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
    	return simpleGrantedAuthorities;
	}



    @Override
    public String getPassword() {
        String userPassword=this.sysUser.getPassword();
		//注意清除密码
		this.sysUser.setPassword(null);
		return userPassword;

    }

    @Override
    public String getUsername() {
        return sysUser.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return sysUser.getAccountNoExpired().equals(1);
    }

    @Override
    public boolean isAccountNonLocked() {
        return sysUser.getAccountNoLocked().equals(1);
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return sysUser.getCredentialsNoExpired().equals(1);
    }

    @Override
    public boolean isEnabled() {
        return sysUser.getEnabled().equals(1);
    }
}


3、认证成功处理器

自定义处理器,实现AuthenticationSuccessHandler,当用户登录认证成功后,会执行这个处理器,即认证成功处理器

/**
 * 认证成功处理器,当用户登录成功后,会执行此处理器
 */
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    //使用此工具类进行序列化
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private JwtUtils jwtUtils;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        //从认证对象中获取认证用户信息
		//查看前面第六章的UserDetailsService接口的loadUserByUsername方法,
		//返回给框架的是一个自定义的SecurityUser对象(Security实现了UserDetails)
        SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
        //从SecurityUser中拿出和底层MYSQL挂钩的SysUser类信息
        String userInfo=objectMapper.writeValueAsString(securityUser.getSysUser());
        List<SimpleGrantedAuthority> authorities = (List<SimpleGrantedAuthority>) securityUser.getAuthorities();
        //List<SimpleGrantedAuthority>转List<String>
        List<String> authList=new ArrayList<>();
        for (SimpleGrantedAuthority authority : authorities) {
            authList.add(authority.getAuthority());
        }
		//也可使用stream流代替上面的for循环
		List<String> authList = authorities.stream().map(
		        a -> {
		           return a.getAuthority();
		        }
		).collect(Collectors.toList());

		//也可使用stream流+Lambda表达式
		List<String> authList = authorities.stream()
		.map(SimpleGrantedAuthority::getAuthority)
		.collect(Collectors.toList());


        // 调用前面的JWT工具类方法创建jwt
        String token = jwtUtils.createToken(userInfo,authList);

        //返回给前端token@Builder模式创建对象
        HttpResult httpResult = HttpResult.builder().code(200).msg("OK").data(token).build();
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write(objectMapper.writeValueAsString(httpResult));
        writer.flush();
    }
}


4、创建JWT过滤器

定义JWT过滤器,用来检验一个个对接口的请求中token是否合法,注意放行登录接口:

/**
 * 定义一次性请求过滤器
 */
@Component
@Slf4j
public class JwtCheckFilter extends OncePerRequestFilter {
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private JwtUtils jwtUtils;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取请求uri
        String requestURI = request.getRequestURI();
        // 如果是登录页面,放行
        if (requestURI.equals("/login")) {
            filterChain.doFilter(request, response);
            return;
        }
        //获取请求头中的Authorization,前端一般这么传,key为Authorization
        String authorization = request.getHeader("Authorization");
        //如果Authorization为空,那么不允许用户访问,直接返回
        if (!StringUtils.hasText(authorization)) {
            printFront(response, "没有登录!");
            return;
        }
        //Authorization 去掉头部的Bearer 信息,获取token值
        String jwtToken = authorization.replace("Bearer ", "");
        //验签
        boolean verifyTokenResult = jwtUtils.verifyToken(jwtToken);
        //验签不成功
        if (!verifyTokenResult) {
            printFront(response, "jwtToken 已过期");
            return;
        }
		//到这儿算是验证通过,但还没结束,还要将信息填充后返给SpringSecurity框架
        //从payload中获取userInfo
        String userInfo = jwtUtils.getUserInfo(jwtToken);
        //从payload中获取授权列表
        List<String> userAuth = jwtUtils.getUserAuth(jwtToken);
        //在认证成功处理器中,创建token时,userInfo里放的是SysUser对象的序列化字符串,这里反序列化
        SysUser sysUser = objectMapper.readValue(userInfo, SysUser.class);
        SecurityUser securityUser = new SecurityUser(sysUser);
        //设置权限
        List<SimpleGrantedAuthority> authList = userAuth.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        securityUser.setAuthorityList(authList);

		//填充信息
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToke = new UsernamePasswordAuthenticationToken(securityUser
                , null, authList);
        //通过安全上下文设置认证信息
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToke);
        //继续访问相应的rul等
        filterChain.doFilter(request, response);

    }
    
    /**
 	* 定义一个通过response向前端返回数据的方法
 	* 这里不是controller层,不是你直接返回个结果类就行的,注意区别
 	*/
    private void printFront(HttpServletResponse response, String message) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        HttpResult httpResult = new HttpResult();
        httpResult.setCode(401);
        httpResult.setMsg(message);

        writer.print(objectMapper.writeValueAsString(httpResult));
        writer.flush();
    }
}


上面的过滤器中,除了正常的验签,最后的消息填充与保存在安全上下文,就是下图中的第十步:

在这里插入图片描述

5、安全配置类

修改下安全配置类,把上面的处理器和过滤器加进来。

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
    @Resource
    private JwtCheckFilter jwtCheckFilter;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	//先看token是否合法,再走框架的用户名密码校验过滤器
        http.addFilterBefore(jwtCheckFilter, UsernamePasswordAuthenticationFilter.class);
        //要是有之间的验证码校验,则它应该在token校验之前
        //认证通过后,走认证成功处理器,颁发token
        http.formLogin().successHandler(myAuthenticationSuccessHandler).permitAll();
        //简单按接口加个权限要求
		http.authorizeRequests()
        .mvcMatchers("/student/**")
        .hasAnyAuthority("student:query","student:update")
		.anyRequest()
		.authenticated(); //任何请求均需要认证(登录成功)才能访问
        http.csrf().disable();  //跨域
        //禁用session方式
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

   
}

效果:
在这里插入图片描述

登录认证后返给前端token:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/960295.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

专业协作设计工具,看看这6个有你喜欢的吗

如今“在线协作”成为了团队设计场景中必需的功能之一&#xff0c;今天本文整理了7个支持团队协作的&#xff0c;好用的设计工具&#xff0c;一起来看看吧&#xff01; 1、即时设计 即时设计是一个强大的在线设计协作工具&#xff0c;它具有实时协作设计和在线评论能力。在设…

干翻Dubbo系列第十四篇:Dubbo协议基于SpringBoot规范化开发

文章目录 文章说明 一&#xff1a;版本控制 二&#xff1a;共有依赖声明于父项目 三&#xff1a;创建共有API 1&#xff1a;定义公共接口 2&#xff1a;定义Bean 四&#xff1a;创建Provider 1&#xff1a;引入公共API 2&#xff1a;创建实现类 3&#xff1a;定义启动…

Mac 如何判断下载Mac with Intel Chip 还是 Mac with Apple Chip

如下图&#xff0c;当我们在 Mac系统 下载客户端时&#xff0c;有两种选择&#xff1a;Mac with Intel Chip 、 Mac with Apple Chip 如何判断要下载哪一种&#xff1f; 需要判断本机Mac是在Inter芯片还是Apple芯片上运行的。方法如下&#xff1a; 点击屏幕左上角Apple标志&a…

DVWA失效的访问控制

失效的访问控制&#xff0c;可以认为是系统对一些功能进行了访问或权限限制&#xff0c;但因为种种原因&#xff0c;限制并没有生效&#xff0c;造成失效的访问控制漏洞,比如越权等 这里以DVWA为例&#xff0c;先访问低难度的命令执行并抓包 删除cookie&#xff0c;并在请求头…

Ei Scopus双检索 | 2024年第二届绿色建筑国际会议(ICoGB 2024)

会议简介 Brief Introduction 2024年第二届绿色建筑国际会议(ICoGB 2024) 会议时间&#xff1a;2024年5月22日-24日 召开地点&#xff1a;意大利米兰 大会官网&#xff1a;www.icogb.org ICoGB 2024将围绕“绿色建筑”的最新研究领域而展开&#xff0c;为研究人员、工程师、专家…

Linux 进程基础概念-进程状态、进程构成、进程控制

Linux 进程 参考&#xff1a; 「linux操作系统」进程的切换与控制到底有啥关系&#xff1f; - 知乎 (zhihu.com)&#xff0c;Linux进程解析_deep_explore的博客-CSDN博客&#xff0c;腾讯面试&#xff1a;进程的那些数据结构 - 知乎 (zhihu.com)&#xff0c;如何在Linux下的进…

百万级并发IM即时消息系统(3)配置数据初始化和前后端交互

04_配置数据初始化及前后端交互_哔哩哔哩_bilibili 1.配置文件 创建一个config文件夹以及一个app.yaml配置文件。 该文件专门存放一些关键配置&#xff0c;如mysql DNS路径和redis的addr账号密码等。 后期可以创建一个工具包和一些初始化方法&#xff0c;专门用来加载这些配…

密码算法、密钥体系---安全行业基础篇1

一、密码算法 密码算法是一种数学和计算方法&#xff0c;用于保护数据的机密性和安全性。不同的密码算法使用不同的数学原理和技术来加密和解密数据。以下是一些常见的密码算法类型&#xff1a; 1. **对称密码算法&#xff1a;** 特点&#xff1a;相同的密钥用于加密和解密数…

14:00面试,14:08就出来了,问的问题有点变态

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%,…

雅思写作 三小时浓缩学习顾家北 笔记总结(三)

目录 顾家北饥饿网100个句子翻译 "Heritage sites threatened by urban development" "Heritage sites are threatened by urban development." We should not ignore face-to-face communication. We cannot ignore face-to-face communication. So…

电商项目part09 分布式事务SeataMQ可靠消息

分布式事务 在微服务架构中&#xff0c;完成某一个业务功能可能需要横跨多个服务&#xff0c;操作多个数据库。这就涉及到到了分布式事务&#xff0c;需要操作的资源位于多个资源服务器上&#xff0c;而应用需要保证对于多个资源服务器的数据操作&#xff0c;要么全部成功&…

数据统计汇总聚合

一些方法 特殊&#xff1a;数据聚合 可加入排序

Leetcode: 1. 两数之和 【题解超详细】

前言 有人夜里挑灯看花&#xff0c;有人相爱&#xff0c;有人夜里开车看海&#xff0c;有人leetcode第一题都做不出来。 希望下面的题解可以帮助你们开始 你们的 leetcode 刷题 的 天降之路 题目 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中…

Linux配置ADSL链接

在Linux中配置ADSL链接&#xff0c;可以按照以下步骤进行&#xff1a; 安装rp-pppoeconf工具&#xff0c;这个工具可以通过终端窗口使用。运行命令“rp-pppoeconf”来配置ADSL链接。终端窗口会显示一个向导模式&#xff0c;用于配置ADSL链接。输入用户名和密码。这些信息是用来…

强强联手 | 大势智慧与山维科技签署战略合作协议

8月30日&#xff0c;武汉大势智慧科技有限公司&#xff08;以下简称“大势智慧”&#xff09;与北京山维科技股份有限公司&#xff08;以下简称“山维科技”&#xff09;达成战略合作。双方将聚焦新型基础测绘建设与实景三维中国领域&#xff0c;携手为广大用户提供数据采集、生…

ShardingJDBC——分库分表实践

摘要 本文主要介绍分表分库&#xff0c;以及SpringBoot集成基于ShardingJDBC的单库分表实践。 一、Sharding-JDBC Sharding-JDBC是ShardingSphere的第一个产品&#xff0c;也是ShardingSphere的前身。 它定位为轻量级Java框架&#xff0c;在Java的JDBC层提供的额外服务。它使…

stable diffusion实践操作-embedding(TEXTUAL INVERSION)

本文专门开一节写图生图相关的内容&#xff0c;在看之前&#xff0c;可以同步关注&#xff1a; stable diffusion实践操作 可以理解为提示词的集合&#xff0c;可以省略大量的提示词。后缀safetensors&#xff0c;大小几十kb 正文 1、功能 可以理解为提示词的集合&#xff0…

IntelliJ IDEA创建Web项目并使用Web服务器----Tomcat

&#x1f3c6;IntelliJ IDEA创建Web项目并使用Web服务器----Tomcat 以下是本篇文章正文内容&#xff0c;下面案例可供参考&#xff08;提示&#xff1a;本篇文章属于原创&#xff0c;请转发或者引用时注明出处。&#xff09;&#xff0c;大家记得支持一下&#xff01;&#xff…

【权限提升-Windows提权】-UAC提权之MSF模块和UACME项目-DLL劫持-不带引号服务路径-不安全的服务权限

权限提升基础信息 1、具体有哪些权限需要我们了解掌握的&#xff1f; 后台权限&#xff0c;网站权限&#xff0c;数据库权限&#xff0c;接口权限&#xff0c;系统权限&#xff0c;域控权限等 2、以上常见权限获取方法简要归类说明&#xff1f; 后台权限&#xff1a;SQL注入,数…

云原生架构-架构师之路(十九)

云原生架构内涵 云原生架构 基于云原生技术&#xff0c;指将 云应用中的非业务代码部分进行最大化的剥离&#xff0c;让 云设施接管项目中大量非功能特性&#xff08;如弹性、韧性、安全、可观测性和灰度等&#xff09;。把质量属性 和 业务功能完全分离&#xff0c;我们自己的…