当前位置: 代码网 > it编程>编程语言>Java > Spring Boot JWT登录授权使用(无感刷新)

Spring Boot JWT登录授权使用(无感刷新)

2026年04月07日 Java 我要评论
一、引言在分布式系统和前后端分离架构中,传统的基于 session 的认证方式存在跨域难处理、服务端存储压力大等问题。jwt(json web token)作为一种轻量级的身份认证与授权方案,凭借其无

一、引言

在分布式系统和前后端分离架构中,传统的基于 session 的认证方式存在跨域难处理、服务端存储压力大等问题。jwt(json web token) 作为一种轻量级的身份认证与授权方案,凭借其无状态、可跨域、易于扩展的特性,成为 spring boot 项目中实现认证授权的主流选择。本文将从环境搭建、核心实现到进阶优化,完整讲解 spring boot 整合 jwt 实现登录认证与接口授权的全流程。

二、技术栈与环境准备

1. 核心依赖

在 spring boot 项目的pom.xml(maven)或build.gradle(gradle)中引入以下

  • spring security:提供认证与授权的基础框架
  • jjwt:java 领域主流的 jwt 工具库,支持 jwt 的生成、解析与验证
  • spring web:用于编写接口测试认证授权流程

maven 依赖配置示例:

<!-- spring boot starter web -->
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-web</artifactid>
</dependency>
<!-- spring security -->
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-security</artifactid>
</dependency>
<!-- jwt -->
<dependency>
    <groupid>io.jsonwebtoken</groupid>
    <artifactid>jjwt-api</artifactid>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupid>io.jsonwebtoken</groupid>
    <artifactid>jjwt-impl</artifactid>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupid>io.jsonwebtoken</groupid>
    <artifactid>jjwt-jackson</artifactid>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

2. 核心概念说明

jwt 结构: 由 header(头部)、payload(载荷)、signature(签名)三部分组成,以.分隔,例如eyjhbgcioijiuzi1niisinr5cci6ikpxvcj9.eyjzdwiioiixmjm0nty3odkwiiwibmftzsi6ikpvag4grg9liiwiawf0ijoxnte2mjm5mdiyfq.sflkxwrjsmekkf2qt4fwpmejf36pok6yjv_adqssw5c

  • header:存储算法类型和令牌类型,如{"alg":"hs256","typ":"jwt"}
  • payload:存储用户身份信息(如用户名、角色)和过期时间等声明,不建议存放敏感信息
  • signature:通过 header 指定的算法,结合密钥对 header 和 payload 进行加密,保证令牌不被篡改

认证流程: 用户登录成功后,服务端生成 jwt 返回给客户端;客户端后续请求携带 jwt,服务端验证令牌有效性后完成授权

三、核心功能实现

1. jwt 工具类封装

@component
public class jwtutils {
    @value("${jwt.secret}")
    private string secret;
    @value("${jwt.access-token-expire-time}")
    private long accesstokenexpiretime;
    @value("${jwt.refresh-token-expire-time}")
    private long refreshtokenexpiretime;
    /**
     * 生成访问令牌
     */
    public string generateaccesstoken(string username) {
        map<string, object> claims = new hashmap<>();
        return generatetoken(claims, username, accesstokenexpiretime);
    }
    /**
     * 生成刷新令牌
     */
    public string generaterefreshtoken(string username) {
        map<string, object> claims = new hashmap<>();
        return generatetoken(claims, username, refreshtokenexpiretime);
    }
    /**
     * 生成token
     */
    private string generatetoken(map<string, object> claims, string subject, long expiretime) {
        secretkey key = keys.hmacshakeyfor(secret.getbytes(standardcharsets.utf_8));
        return jwts.builder()
                .setclaims(claims)
                .setsubject(subject)
                .setissuedat(new date())
                .setexpiration(new date(system.currenttimemillis() + expiretime))
                .signwith(key, signaturealgorithm.hs512)
                .compact();
    }
    /**
     * 从token中获取用户名
     */
    public string getusernamefromtoken(string token) {
        claims claims = getclaimsfromtoken(token);
        return claims.getsubject();
    }
    /**
     * 验证token是否有效
     */
    public boolean validatetoken(string token) {
        try {
            secretkey key = keys.hmacshakeyfor(secret.getbytes(standardcharsets.utf_8));
            jwts.parserbuilder()
                .setsigningkey(key)
                .build()
                .parseclaimsjws(token);
            return true;
        } catch (exception e) {
            return false;
        }
    }
    /**
     * 判断token是否过期
     */
    public boolean istokenexpired(string token) {
        claims claims = getclaimsfromtoken(token);
        date expiration = claims.getexpiration();
        return expiration.before(new date());
    }
    /**
     * 从token中获取claims
     */
    private claims getclaimsfromtoken(string token) {
        secretkey key = keys.hmacshakeyfor(secret.getbytes(standardcharsets.utf_8));
        return jwts.parserbuilder()
                .setsigningkey(key)
                .build()
                .parseclaimsjws(token)
                .getbody();
    }
}

2. 实现认证过滤器

@component
public class jwtauthenticationfilter extends onceperrequestfilter {
    @autowired
    private jwtutils jwtutils;
    @autowired
    private userdetailsservice userdetailsservice;
    @override
    protected void dofilterinternal(httpservletrequest request, httpservletresponse response, filterchain chain) 
            throws servletexception, ioexception {
        // 跳过登录和刷新令牌的接口
        string requesturi = request.getrequesturi();
        if (requesturi.equals("/api/auth/login") || requesturi.equals("/api/auth/refresh")) {
            chain.dofilter(request, response);
            return;
        }
        // 从请求头获取token
        string token = gettokenfromrequest(request);
        if (stringutils.hastext(token) && jwtutils.validatetoken(token)) {
            string username = jwtutils.getusernamefromtoken(token);
            userdetails userdetails = userdetailsservice.loaduserbyusername(username);
            // 设置认证信息到上下文
            usernamepasswordauthenticationtoken authentication = new usernamepasswordauthenticationtoken(
                    userdetails, null, userdetails.getauthorities());
            authentication.setdetails(new webauthenticationdetailssource().builddetails(request));
            securitycontextholder.getcontext().setauthentication(authentication);
        } else {
            securitycontextholder.clearcontext();
        }
        chain.dofilter(request, response);
    }
    private string gettokenfromrequest(httpservletrequest request) {
        string bearertoken = request.getheader("authorization");
        if (stringutils.hastext(bearertoken) && bearertoken.startswith("bearer ")) {
            return bearertoken.substring(7);
        }
        return null;
    }
}

3. 实现无感刷新令牌过滤器

创建jwtrefreshfilter实现令牌的无感刷新:

@component
public class jwtrefreshfilter extends onceperrequestfilter {
    @autowired
    private jwtutils jwtutils;
    @autowired
    private userdetailsservice userdetailsservice;
    // 当token剩余有效期小于10分钟时,自动刷新
    private static final long refresh_threshold = 10 * 60 * 1000;
    @override
    protected void dofilterinternal(httpservletrequest request, httpservletresponse response, filterchain filterchain) 
            throws servletexception, ioexception {
        // 跳过登录和刷新接口
        string requesturi = request.getrequesturi();
        if (requesturi.equals("/api/auth/login") || requesturi.equals("/api/auth/refresh")) {
            filterchain.dofilter(request, response);
            return;
        }
        // 获取请求头中的token
        string authorizationheader = request.getheader("authorization");
        if (authorizationheader == null || !authorizationheader.startswith("bearer ")) {
            filterchain.dofilter(request, response);
            return;
        }
        string token = authorizationheader.substring(7);
        try {
            if (jwtutils.validatetoken(token)) {
                // 检查token是否即将过期
                long remainingtime = jwtutils.gettokenremainingtime(token);
                if (remainingtime < refresh_threshold) {
                    string username = jwtutils.getusernamefromtoken(token);
                    string newaccesstoken = jwtutils.generateaccesstoken(username);
                    // 将新token添加到响应头
                    response.setheader("authorization", "bearer " + newaccesstoken);
                }
            }
        } catch (exception e) {
            securitycontextholder.clearcontext();
        }
        filterchain.dofilter(request, response);
    }
}

4. 配置 spring security

创建securityconfig配置安全规则:

@configuration
@enablewebsecurity
@enablemethodsecurity(prepostenabled = true)
public class securityconfig {
    @bean
    public passwordencoder passwordencoder() {
        return new bcryptpasswordencoder();
    }
    @bean
    public authenticationmanager authenticationmanager(authenticationconfiguration authenticationconfiguration) throws exception {
        return authenticationconfiguration.getauthenticationmanager();
    }
    @bean
    public securityfilterchain securityfilterchain(httpsecurity http, jwtauthenticationfilter jwtauthfilter, 
                                                  jwtrefreshfilter jwtrefreshfilter) throws exception {
        http
            .cors().and().csrf().disable()
            .sessionmanagement().sessioncreationpolicy(sessioncreationpolicy.stateless)
            .and()
            .authorizehttprequests()
            .antmatchers("/api/auth/**", "/api/public/**").permitall()
            .anyrequest().authenticated()
            .and()
            .exceptionhandling()
            .authenticationentrypoint((request, response, ex) -> {
                response.setstatus(httpservletresponse.sc_unauthorized);
                response.setcontenttype("application/json");
                response.getwriter().write("{\"error\":\"unauthorized\",\"message\":\"authentication required\"}");
            });
        // 添加jwt过滤器
        http.addfilterbefore(jwtauthfilter, usernamepasswordauthenticationfilter.class);
        http.addfilterbefore(jwtrefreshfilter, usernamepasswordauthenticationfilter.class);
        return http.build();
    }
}

5. 实现认证服务

创建authservice接口和实现类处理登录和刷新令牌业务:

public interface authservice {
    loginresponsedto login(loginrequestdto loginrequest);
    loginresponsedto refreshtoken(refreshtokenrequestdto refreshtokenrequest);
}
@service
public class authserviceimpl implements authservice {
    @autowired
    private authenticationmanager authenticationmanager;
    @autowired
    private jwtutils jwtutils;
    @autowired
    private stringredistemplate stringredistemplate;
    @value("${jwt.refresh-token-expire-time}")
    private long refreshtokenexpiretime;
    @override
    public loginresponsedto login(loginrequestdto loginrequest) {
        // 验证输入
        if (loginrequest == null || stringutils.isempty(loginrequest.getusername()) || 
            stringutils.isempty(loginrequest.getpassword())) {
            throw new badcredentialsexception("invalid login request");
        }
        try {
            // 执行认证
            authentication authentication = authenticationmanager.authenticate(
                new usernamepasswordauthenticationtoken(
                    loginrequest.getusername(), 
                    loginrequest.getpassword()
                )
            );
            securitycontextholder.getcontext().setauthentication(authentication);
            // 生成token
            string username = loginrequest.getusername();
            string accesstoken = jwtutils.generateaccesstoken(username);
            string refreshtoken = jwtutils.generaterefreshtoken(username);
            // 存储refresh token到redis
            string refreshtokenkey = "refresh_token:" + username;
            stringredistemplate.opsforvalue().set(
                refreshtokenkey, 
                refreshtoken, 
                refreshtokenexpiretime, 
                timeunit.milliseconds
            );
            // 构建响应
            loginresponsedto response = new loginresponsedto();
            response.setaccesstoken(accesstoken);
            response.setrefreshtoken(refreshtoken);
            response.setexpiresin(refreshtokenexpiretime);
            return response;
        } catch (authenticationexception e) {
            throw new badcredentialsexception("invalid username or password");
        }
    }
    @override
    public loginresponsedto refreshtoken(refreshtokenrequestdto refreshtokenrequest) {
        // 验证输入
        if (refreshtokenrequest == null || stringutils.isempty(refreshtokenrequest.getrefreshtoken())) {
            throw new badcredentialsexception("invalid refresh token request");
        }
        string refreshtoken = refreshtokenrequest.getrefreshtoken();
        // 验证refresh token
        if (!jwtutils.validatetoken(refreshtoken)) {
            throw new badcredentialsexception("invalid refresh token");
        }
        string username = jwtutils.getusernamefromtoken(refreshtoken);
        if (stringutils.isempty(username)) {
            throw new badcredentialsexception("invalid refresh token");
        }
        // 验证redis中存储的refresh token
        string storedtoken = stringredistemplate.opsforvalue().get("refresh_token:" + username);
        if (storedtoken == null || !storedtoken.equals(refreshtoken)) {
            throw new badcredentialsexception("invalid refresh token");
        }
        // 生成新的token
        string newaccesstoken = jwtutils.generateaccesstoken(username);
        string newrefreshtoken = jwtutils.generaterefreshtoken(username);
        // 更新redis中的refresh token
        stringredistemplate.opsforvalue().set(
            "refresh_token:" + username, 
            newrefreshtoken, 
            refreshtokenexpiretime, 
            timeunit.milliseconds
        );
        loginresponsedto response = new loginresponsedto();
        response.setaccesstoken(newaccesstoken);
        response.setrefreshtoken(newrefreshtoken);
        response.setexpiresin(refreshtokenexpiretime);
        return response;
    }
}

四、相关注解总结

注解名称                          核心作用                                        使用场景                                                            关键注意事项                                                                            
@enablewebsecurity          开启 spring security 的 web 安全功能,加载安全过滤器链和相关配置标注在自定义的 spring security 配置类上                                    必须搭配@configuration使用,否则配置不生效                                                    
@enableglobalmethodsecurity开启方法级权限控制,支持多种权限注解                          标注在 security 配置类上,需指定启用的注解类型                                    常用属性:prepostenabled=true(启用@preauthorize等)、securedenabled=true(启用@secured)
@preauthorize              方法执行前校验权限,支持 spel 表达式,可实现复杂权限判断            控制器接口方法、服务层方法的权限控制(细粒度权限)                                      依赖@enableglobalmethodsecurity(prepostenabled=true),支持角色、权限、请求参数校验                
@postauthorize              方法执行后校验权限,基于方法返回值判断                        需根据返回结果控制权限的场景(极少使用,避免方法执行产生副作用)                                spel 表达式中用returnobject指代方法返回值                                                    
@secured                    基于角色的粗粒度权限控制                                控制器或服务层方法的角色校验                                                  需启用securedenabled=true,角色名称必须以role_为前缀                                        
@rolesallowed              jsr-250 规范注解,基于角色的权限控制                      控制器或服务层方法的多角色授权                                                需启用jsr250enabled=true,角色名称可省略role_前缀(框架自动补充)                                  
@authenticationprincipal    直接获取当前认证用户的userdetails或自定义用户信息            控制器方法参数中,需获取当前登录用户信息时                                          无需手动从securitycontextholder获取,直接注入即可                                              
@prefilter                  方法执行前,对集合类型参数进行过滤,仅保留符合权限的元素                数据查询前的参数过滤(数据级权限控制)                                            仅对集合类型参数生效,spel 表达式用filterobject指代集合元素                                          
@postfilter                方法执行后,对集合类型返回值进行过滤,仅保留符合权限的元素              数据返回后的结果过滤(数据级权限控制)                                            仅对集合类型返回值生效,依赖@enableglobalmethodsecurity启用                                      

总结

本指南介绍了如何在 spring boot 应用中实现基于 jwt 的认证授权功能,包括:

  • jwt 令牌的生成与验证
  • 基于 spring security 的认证过滤器
  • 令牌刷新机制与无感刷新实现
  • 完整的登录与授权流程

通过这种方式,我们可以实现无状态的认证系统,适合分布式应用和前后端分离架构。实际应用中,还可以根据需要添加更多功能,如令牌撤销、角色权限控制等。

到此这篇关于spring boot jwt登录授权使用(无感刷新))的文章就介绍到这了,更多相关spring boot jwt登录授权内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com