当前位置: 代码网 > it编程>编程语言>Java > SpringBoot整合JWT实现登录认证与接口授权的全流程

SpringBoot整合JWT实现登录认证与接口授权的全流程

2025年12月15日 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_为前缀
@rolesallowedjsr-250 规范注解,基于角色的权限控制    控制器或服务层方法的多角色授权 需启用jsr250enabled=true,角色名称可省略role_前缀(框架自动补充)
@authenticationprincipal   直接获取当前认证用户的userdetails或自定义用户信息控制器方法参数中,需获取当前登录用户信息时  无需手动从securitycontextholder获取,直接注入即可 
@prefilter方法执行前,对集合类型参数进行过滤,仅保留符合权限的元素  数据查询前的参数过滤(数据级权限控制)        仅对集合类型参数生效,spel 表达式用filterobject指代集合元素                    
@postfilter方法执行后,对集合类型返回值进行过滤,仅保留符合权限的元素数据返回后的结果过滤(数据级权限控制)        仅对集合类型返回值生效,依赖@enableglobalmethodsecurity启用              

总结

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

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

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

以上就是springboot整合jwt实现登录认证与接口授权的全流程的详细内容,更多关于springboot jwt登录认证与接口授权的资料请关注代码网其它相关文章!

(1)

相关文章:

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

发表评论

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