当前位置: 代码网 > it编程>编程语言>Java > JWT + Spring Security / OAuth2.0:微服务统一登录、鉴权、单点登录全解析

JWT + Spring Security / OAuth2.0:微服务统一登录、鉴权、单点登录全解析

2026年04月10日 Java 我要评论
在微服务架构中,服务被拆分为多个独立部署的节点,跨服务访问、用户身份统一管理成为核心痛点——用户在每个服务都需单独登录、权限无法统一管控、多系统切换频繁登录,这些问题不仅影响用

在微服务架构中,服务被拆分为多个独立部署的节点,跨服务访问、用户身份统一管理成为核心痛点——用户在每个服务都需单独登录、权限无法统一管控、多系统切换频繁登录,这些问题不仅影响用户体验,更会带来严重的安全隐患。

一、微服务身份认证与鉴权的核心痛点

在单体应用中,我们通常通过session存储用户身份信息,实现登录与鉴权,但这种方式在微服务架构中完全失效,核心痛点集中在3点:

1.1 session共享问题

单体应用中,session存储在服务器内存,微服务中多个服务部署在不同节点,session无法跨服务共享,导致用户在a服务登录后,访问b服务仍需重新登录,体验极差。

1.2 权限管控分散

每个微服务单独维护一套权限规则,无法实现统一的角色、资源管控,不仅开发冗余,更易出现权限漏洞(如某服务遗漏权限校验、权限规则不一致)。

1.3 多系统单点登录需求

企业通常有多个关联系统(如电商系统、后台管理系统、app接口),用户希望一次登录,即可访问所有授权系统,无需重复输入账号密码,这就需要单点登录(sso)能力。

而jwt + spring security + oauth2.0的组合,正是解决上述痛点的最优解:jwt实现无状态令牌传输,spring security实现权限管控,oauth2.0实现授权与单点登录,三者协同,构建微服务统一身份认证与鉴权体系。

二、jwt、spring security、oauth2.0

2.1 jwt:无状态令牌,解决session共享难题

jwt(json web token)是一种轻量级的令牌规范,核心作用是在客户端与服务器之间安全地传输用户身份信息,采用无状态设计,无需在服务器存储session,完美适配微服务架构。

2.1.1 jwt核心结构(3部分,用点号分隔)

jwt令牌由 header(头部)payload(载荷)signature(签名) 三部分组成,示例:

eyjhbgcioijiuzi1niisinr5cci6ikpxvcj9.eyj1c2vyswqiojesinvzzxjoyw1lijoiywrtaw4ilcjlehaioje3mtuyodc2mdasimlhdci6mtcxnti4ndawmh0.sflkxwrjsmekkf2qt4fwpmejf36pok6yjv_adqssw5c

header(头部):指定jwt的签名算法和令牌类型,默认算法为hs256(hmac sha256),示例:

{
  "alg": "hs256", // 签名算法
  "typ": "jwt" // 令牌类型
}
  • payload(载荷):存储用户核心信息(如用户id、用户名、角色)和令牌过期时间,分为标准声明自定义声明
{ "userid": 1, // 自定义声明:用户id "username": "admin", // 自定义声明:用户名 "role": "admin", // 自定义声明:角色 "exp": 1715287600, // 标准声明:过期时间 "iat": 1715284000 // 标准声明:签发时间 }
  • 标准声明(可选,但推荐使用):
    • sub:令牌面向的用户
    • iat:令牌签发时间
    • exp:令牌过期时间(时间戳,单位毫秒)
    • iss:令牌签发者
    • 自定义声明:根据业务需求添加,如用户id(userid)、角色(role)、权限(permissions)等,注意:payload不加密,不能存储敏感信息(如密码)。
  • signature(签名):核心安全保障,通过header指定的算法,将header(base64编码)、payload(base64编码)和密钥(secret)进行加密,生成签名。服务器接收令牌后,会重新计算签名,若与令牌中的签名不一致,则说明令牌被篡改,直接拒绝访问。 签名计算公式:hmacsha256( base64encode(header) + "." + base64encode(payload), secret )

2.1.2 jwt核心优势与注意事项

  • 优势
    • 无状态:服务器无需存储session,仅通过令牌即可验证用户身份,减轻服务器压力,适配微服务集群部署;
    • 跨语言:基于json格式,支持所有语言(java、python、go等),适配多端(web、app、小程序);
    • 自包含:payload中包含用户核心信息,无需频繁查询数据库,提升接口响应速度;
    • 可扩展:支持自定义声明,适配不同业务场景的身份信息传输需求。
  • 注意事项
    • payload不加密:严禁存储敏感信息(如密码、手机号),仅存储非敏感的用户标识和权限信息;
    • 密钥安全:签名密钥(secret)必须妥善保管,一旦泄露,攻击者可伪造令牌,引发安全风险;
    • 令牌过期:必须设置合理的过期时间(如1小时),过期后需重新登录获取新令牌;
    • 无法撤销:jwt令牌一旦签发,在过期前无法主动撤销(除非结合redis黑名单机制)。

2.2 spring security:微服务权限管控核心框架

spring security是spring生态中成熟的权限管理框架,核心作用是实现用户认证(登录校验)和授权(权限管控),提供了完善的安全防护机制(如csrf防护、xss防护、会话管理),可无缝整合jwt和oauth2.0,是微服务权限管控的首选框架。

2.2.1 spring security核心概念

  • 认证(authentication):验证用户身份的合法性(如账号密码是否正确),认证通过后,生成认证信息(authentication对象),存储在securitycontext中。
  • 授权(authorization):验证用户是否拥有访问某个资源的权限(如普通用户能否访问管理员接口),核心是“资源-角色-用户”的关联关系。
  • securitycontext:存储用户认证信息的上下文,线程安全,可通过securitycontextholder.getcontext()获取当前登录用户信息。
  • userdetailsservice:核心接口,用于加载用户信息(如从数据库查询用户账号、密码、角色),是认证流程的核心组件。
  • passwordencoder:密码加密器,用于对用户密码进行加密存储(如bcrypt加密),避免明文存储密码,提升安全性。
  • filterchain:安全过滤器链,spring security通过一系列过滤器(如usernamepasswordauthenticationfilter、jwtauthenticationfilter)处理请求,完成认证和授权。

2.2.2 spring security核心流程(认证+授权)

  • 用户发起登录请求(如post /login),携带账号密码;
  • usernamepasswordauthenticationfilter拦截请求,将账号密码封装为authentication对象;
  • 调用authenticationmanager(认证管理器),触发认证流程;
  • authenticationmanager调用userdetailsservice,加载数据库中的用户信息(userdetails);
  • passwordencoder对比用户提交的密码与数据库中加密后的密码,验证是否一致;
  • 认证通过:生成包含用户信息和权限的authentication对象,存入securitycontext;
  • 认证失败:抛出异常,返回登录失败提示;
  • 用户访问受保护资源时,filtersecurityinterceptor拦截请求,校验当前用户是否拥有该资源的访问权限;
  • 授权通过:允许访问资源;授权失败:返回403 forbidden。

2.3 oauth2.0:授权协议,实现单点登录与第三方授权

oauth2.0是一种开放的授权协议,核心作用是实现“第三方授权”和“单点登录(sso)”,允许用户通过一个账号(如微信、qq)登录多个关联系统,无需重复注册和登录,同时避免用户将核心账号密码泄露给第三方系统。

注意:oauth2.0是授权协议,不是认证协议,它的核心是“授权”——用户授权第三方系统访问自己的资源(如微信授权某app获取用户昵称、头像),而认证是验证用户身份的过程(如微信登录时验证账号密码)。

2.3.1 oauth2.0核心角色

  • 资源所有者(resource owner):用户,拥有资源的所有权(如微信用户拥有自己的昵称、头像等资源)。
  • 客户端(client):需要获取用户资源的应用(如某app、某网站),需提前在授权服务器注册,获取客户端id(client_id)和客户端密钥(client_secret)。
  • 授权服务器(authorization server):负责验证用户身份、颁发授权令牌(如access_token),是oauth2.0的核心组件(如微信授权服务器)。
  • 资源服务器(resource server):存储用户资源的服务器(如微信的用户信息服务器),客户端通过授权令牌访问资源服务器,获取用户资源。

2.3.2 oauth2.0核心授权流程(通用流程)

  • 客户端(app)引导用户跳转到授权服务器,请求用户授权;
  • 用户验证身份(如登录微信),并同意授权客户端访问自己的资源;
  • 授权服务器颁发授权码(code)给客户端;
  • 客户端携带授权码(code)、客户端id、客户端密钥,向授权服务器请求访问令牌(access_token)
  • 授权服务器验证信息无误后,颁发access_token(访问令牌)和refresh_token(刷新令牌)给客户端;
  • 客户端携带access_token,向资源服务器请求访问用户资源;
  • 资源服务器验证access_token的合法性,验证通过后,返回用户资源给客户端。

2.3.3 oauth2.0 4种授权模式(重点掌握2种)

oauth2.0提供4种授权模式,适配不同的业务场景,其中授权码模式密码模式是微服务中最常用的两种。

  • 授权码模式(authorization code)
    • 特点:最安全、最常用的模式,通过授权码获取access_token,避免直接传递账号密码,适合web应用、app等场景;
    • 适用场景:单点登录(sso)、第三方授权(如app用微信登录);
    • 核心优势:安全性高,授权码仅短期有效,且客户端无需存储用户账号密码。
  • 密码模式(password)
    • 特点:用户直接向客户端提供账号密码,客户端携带账号密码向授权服务器请求access_token;
    • 适用场景:微服务内部系统(如后台管理系统),客户端与授权服务器属于同一信任体系,且用户信任客户端;
    • 注意:安全性较低,仅适用于内部信任场景,严禁用于第三方授权。
  • 简化模式(implicit):无需授权码,直接颁发access_token,安全性低,仅适用于纯前端应用(如vue、react),不推荐生产使用。
  • 客户端凭证模式(client credentials):客户端通过自身的client_id和client_secret获取access_token,无需用户参与,适用于服务间通信(如微服务a调用微服务b)。

2.3.4 oauth2.0核心令牌

  • access_token(访问令牌):用于访问资源服务器的令牌,短期有效(如1小时),过期后需重新获取;
  • refresh_token(刷新令牌):用于在access_token过期后,无需重新登录,直接获取新的access_token,长期有效(如7天);
  • 授权码(code):用于获取access_token的临时凭证,短期有效(如5分钟),一次使用后失效。

三、实操落地:spring security + jwt 实现微服务统一登录与鉴权

先实现最基础的“统一登录与鉴权”:基于spring security + jwt,实现用户登录生成jwt令牌,后续请求携带令牌完成身份验证和权限管控,适配微服务架构(无状态)。

3.1 第一步:导入依赖(maven)

<!-- spring boot 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>
<!-- 数据库依赖(模拟用户数据,可替换为mysql) -->
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-data-jpa</artifactid>
</dependency>
<dependency>
    <groupid>com.h2database</groupid>
    <artifactid>h2</artifactid>
    <scope>runtime</scope>
</dependency>

3.2 第二步:配置jwt工具类(生成令牌、验证令牌)

核心工具类,负责jwt令牌的生成、解析、验证,封装通用方法,便于后续调用。

import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.value;
import org.springframework.security.core.userdetails.userdetails;
import org.springframework.stereotype.component;
import java.util.date;
import java.util.hashmap;
import java.util.map;
import java.util.function.function;
@component
public class jwttokenutil {
    // jwt签名密钥(生产环境需配置在配置中心,如nacos,严禁硬编码)
    @value("${jwt.secret}")
    private string secret;
    // jwt过期时间(单位:毫秒,此处配置1小时)
    @value("${jwt.expiration}")
    private long expiration;
    // 从令牌中获取用户名
    public string getusernamefromtoken(string token) {
        return getclaimfromtoken(token, claims::getsubject);
    }
    // 从令牌中获取过期时间
    public date getexpirationdatefromtoken(string token) {
        return getclaimfromtoken(token, claims::getexpiration);
    }
    // 从令牌中获取自定义声明
    public <t> t getclaimfromtoken(string token, function<claims, t> claimsresolver) {
        final claims claims = getallclaimsfromtoken(token);
        return claimsresolver.apply(claims);
    }
    // 解析令牌,获取所有声明(需验证签名)
    private claims getallclaimsfromtoken(string token) {
        return jwts.parserbuilder()
                .setsigningkey(secret.getbytes())
                .build()
                .parseclaimsjws(token)
                .getbody();
    }
    // 判断令牌是否过期
    private boolean istokenexpired(string token) {
        final date expiration = getexpirationdatefromtoken(token);
        return expiration.before(new date());
    }
    // 生成jwt令牌(基于用户信息)
    public string generatetoken(userdetails userdetails) {
        map<string, object> claims = new hashmap<>();
        // 自定义声明:添加用户角色(可根据需求添加更多信息)
        claims.put("roles", userdetails.getauthorities());
        return dogeneratetoken(claims, userdetails.getusername());
    }
    // 生成令牌核心方法
    private string dogeneratetoken(map<string, object> claims, string subject) {
        return jwts.builder()
                .setclaims(claims) // 自定义声明
                .setsubject(subject) // 用户名(唯一标识)
                .setissuedat(new date(system.currenttimemillis())) // 签发时间
                .setexpiration(new date(system.currenttimemillis() + expiration)) // 过期时间
                .signwith(signaturealgorithm.hs256, secret.getbytes()) // 签名算法+密钥
                .compact();
    }
    // 验证令牌(验证签名、过期时间、用户名匹配)
    public boolean validatetoken(string token, userdetails userdetails) {
        final string username = getusernamefromtoken(token);
        return (username.equals(userdetails.getusername()) && !istokenexpired(token));
    }
}

3.3 第三步:配置spring security核心配置

核心配置类,用于配置认证流程、授权规则、jwt过滤器等,替代默认的session认证。

import org.springframework.beans.factory.annotation.autowired;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.security.authentication.authenticationmanager;
import org.springframework.security.authentication.dao.daoauthenticationprovider;
import org.springframework.security.config.annotation.authentication.configuration.authenticationconfiguration;
import org.springframework.security.config.annotation.method.configuration.enableglobalmethodsecurity;
import org.springframework.security.config.annotation.web.builders.httpsecurity;
import org.springframework.security.config.annotation.web.configuration.enablewebsecurity;
import org.springframework.security.config.http.sessioncreationpolicy;
import org.springframework.security.core.userdetails.userdetailsservice;
import org.springframework.security.crypto.bcrypt.bcryptpasswordencoder;
import org.springframework.security.crypto.password.passwordencoder;
import org.springframework.security.web.securityfilterchain;
import org.springframework.security.web.authentication.usernamepasswordauthenticationfilter;
@configuration
@enablewebsecurity // 启用spring security
@enableglobalmethodsecurity(prepostenabled = true) // 启用方法级别的权限控制
public class securityconfig {
    @autowired
    private userdetailsservice userdetailsservice;
    @autowired
    private jwtauthenticationfilter jwtauthenticationfilter;
    @autowired
    private jwtauthenticationentrypoint jwtauthenticationentrypoint;
    // 密码加密器(bcrypt加密,不可逆,安全性高)
    @bean
    public passwordencoder passwordencoder() {
        return new bcryptpasswordencoder();
    }
    // 认证提供者(关联userdetailsservice和passwordencoder)
    @bean
    public daoauthenticationprovider authenticationprovider() {
        daoauthenticationprovider authprovider = new daoauthenticationprovider();
        authprovider.setuserdetailsservice(userdetailsservice);
        authprovider.setpasswordencoder(passwordencoder());
        return authprovider;
    }
    // 认证管理器(核心认证组件)
    @bean
    public authenticationmanager authenticationmanager(authenticationconfiguration authconfig) throws exception {
        return authconfig.getauthenticationmanager();
    }
    // 核心安全配置(配置授权规则、过滤器、会话管理等)
    @bean
    public securityfilterchain filterchain(httpsecurity http) throws exception {
        http
                // 关闭csrf防护(微服务中jwt无状态,无需csrf)
                .csrf().disable()
                // 配置未认证请求的处理方式(返回401)
                .exceptionhandling().authenticationentrypoint(jwtauthenticationentrypoint).and()
                // 配置会话管理:无状态(不创建session)
                .sessionmanagement().sessioncreationpolicy(sessioncreationpolicy.stateless).and()
                // 配置授权规则
                .authorizerequests()
                // 登录接口、注册接口允许匿名访问
                .antmatchers("/api/auth/login", "/api/auth/register").permitall()
                // 静态资源允许匿名访问
                .antmatchers("/static/**", "/swagger-ui/**").permitall()
                // 管理员接口仅允许admin角色访问
                .antmatchers("/api/admin/**").hasrole("admin")
                // 普通用户接口允许user或admin角色访问
                .antmatchers("/api/user/**").hasanyrole("user", "admin")
                // 其他所有请求都需要认证
                .anyrequest().authenticated();
        // 注册认证提供者
        http.authenticationprovider(authenticationprovider());
        // 添加jwt过滤器(在用户名密码过滤器之前执行,先验证令牌)
        http.addfilterbefore(jwtauthenticationfilter, usernamepasswordauthenticationfilter.class);
        return http.build();
    }
}

3.4 第四步:实现jwt过滤器与认证异常处理

jwt过滤器负责拦截所有请求,提取请求头中的jwt令牌,验证令牌合法性,若验证通过,将用户信息存入securitycontext,实现无状态认证。

4.1 jwt过滤器(jwtauthenticationfilter)

import org.springframework.beans.factory.annotation.autowired;
import org.springframework.security.authentication.usernamepasswordauthenticationtoken;
import org.springframework.security.core.context.securitycontextholder;
import org.springframework.security.core.userdetails.userdetails;
import org.springframework.security.core.userdetails.userdetailsservice;
import org.springframework.security.web.authentication.webauthenticationdetailssource;
import org.springframework.stereotype.component;
import org.springframework.web.filter.onceperrequestfilter;
import javax.servlet.filterchain;
import javax.servlet.servletexception;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import java.io.ioexception;
@component
public class jwtauthenticationfilter extends onceperrequestfilter {
    @autowired
    private jwttokenutil jwttokenutil;
    @autowired
    private userdetailsservice userdetailsservice;
    // 拦截请求,验证jwt令牌
    @override
    protected void dofilterinternal(httpservletrequest request, httpservletresponse response, filterchain filterchain)
            throws servletexception, ioexception {
        try {
            // 1. 从请求头中提取jwt令牌(请求头格式:authorization: bearer <token>)
            string jwt = getjwtfromrequest(request);
            // 2. 验证令牌是否存在且有效
            if (jwt != null && !jwt.isempty() && jwttokenutil.validatetoken(jwt, userdetailsservice.loaduserbyusername(jwttokenutil.getusernamefromtoken(jwt)))) {
                // 3. 从令牌中获取用户名,加载用户信息
                string username = jwttokenutil.getusernamefromtoken(jwt);
                userdetails userdetails = userdetailsservice.loaduserbyusername(username);
                // 4. 创建认证对象,存入securitycontext
                usernamepasswordauthenticationtoken authentication = new usernamepasswordauthenticationtoken(
                        userdetails, null, userdetails.getauthorities());
                authentication.setdetails(new webauthenticationdetailssource().builddetails(request));
                securitycontextholder.getcontext().setauthentication(authentication);
            }
        } catch (exception e) {
            logger.error("无法设置用户认证信息: {}", e);
        }
        // 继续执行过滤器链
        filterchain.dofilter(request, response);
    }
    // 从请求头中提取jwt令牌
    private string getjwtfromrequest(httpservletrequest request) {
        string bearertoken = request.getheader("authorization");
        if (bearertoken != null && bearertoken.startswith("bearer ")) {
            return bearertoken.substring(7); // 截取bearer后面的令牌部分
        }
        return null;
    }
}

4.2 认证异常处理(jwtauthenticationentrypoint)

当令牌无效、过期或未携带令牌时,返回统一的401响应,替代默认的登录页面。

import org.springframework.security.core.authenticationexception;
import org.springframework.security.web.authenticationentrypoint;
import org.springframework.stereotype.component;
import javax.servlet.servletexception;
import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import java.io.ioexception;
@component
public class jwtauthenticationentrypoint implements authenticationentrypoint {
    // 未认证请求的处理逻辑
    @override
    public void commence(httpservletrequest request, httpservletresponse response, authenticationexception authexception)
            throws ioexception, servletexception {
        // 设置响应状态码401,返回json格式的错误信息
        response.setstatus(httpservletresponse.sc_unauthorized);
        response.setcontenttype("application/json");
        response.getwriter().write("{\"code\":401,\"message\":\"未认证,请先登录获取令牌\"}");
    }
}

3.5 第五步:实现userdetailsservice(加载用户信息)

自定义userdetailsservice,从数据库中加载用户账号、密码、角色信息,适配spring security的认证流程。

5.1 实体类(user)

import lombok.data;
import org.springframework.security.core.grantedauthority;
import org.springframework.security.core.authority.simplegrantedauthority;
import org.springframework.security.core.userdetails.userdetails;
import javax.persistence.*;
import java.util.arraylist;
import java.util.collection;
import java.util.list;
@data
@entity
@table(name = "sys_user")
public class user implements userdetails {
    @id
    @generatedvalue(strategy = generationtype.identity)
    private long id;
    @column(unique = true, nullable = false)
    private string username; // 用户名(唯一)
    @column(nullable = false)
    private string password; // 加密后的密码
    private string role; // 角色(如admin、user)
    // 实现userdetails接口方法:获取用户权限
    @override
    public collection<? extends grantedauthority> getauthorities() {
        list<grantedauthority> authorities = new arraylist<>();
        // 将角色转换为spring security认可的权限格式(role_前缀)
        authorities.add(new simplegrantedauthority("role_" + role));
        return authorities;
    }
    // 实现userdetails接口方法:账号是否未过期(默认true)
    @override
    public boolean isaccountnonexpired() {
        return true;
    }
    // 实现userdetails接口方法:账号是否未锁定(默认true)
    @override
    public boolean isaccountnonlocked() {
        return true;
    }
    // 实现userdetails接口方法:凭证是否未过期(默认true)
    @override
    public boolean iscredentialsnonexpired() {
        return true;
    }
    // 实现userdetails接口方法:账号是否启用(默认true)
    @override
    public boolean isenabled() {
        return true;
    }
}

5.2 userrepository(数据库操作)

import org.springframework.data.jpa.repository.jparepository;
import org.springframework.stereotype.repository;
import java.util.optional;
@repository
public interface userrepository extends jparepository<user, long> {
    // 根据用户名查询用户(spring security认证核心方法)
    optional<user> findbyusername(string username);
}

5.3 自定义userdetailsservice实现

import org.springframework.beans.factory.annotation.autowired;
import org.springframework.security.core.userdetails.userdetails;
import org.springframework.security.core.userdetails.userdetailsservice;
import org.springframework.security.core.userdetails.usernamenotfoundexception;
import org.springframework.stereotype.service;
@service
public class customuserdetailsservice implements userdetailsservice {
    @autowired
    private userrepository userrepository;
    // 加载用户信息(根据用户名)
    @override
    public userdetails loaduserbyusername(string username) throws usernamenotfoundexception {
        // 从数据库查询用户,若不存在则抛出异常
        user user = userrepository.findbyusername(username)
                .orelsethrow(() -> new usernamenotfoundexception("用户不存在:" + username));
        // 返回user对象(已实现userdetails接口)
        return user;
    }
}

3.6 第六步:实现登录接口(生成jwt令牌)

自定义登录接口,接收用户账号密码,完成认证后,生成jwt令牌并返回给客户端。

import org.springframework.beans.factory.annotation.autowired;
import org.springframework.http.responseentity;
import org.springframework.security.authentication.authenticationmanager;
import org.springframework.security.authentication.usernamepasswordauthenticationtoken;
import org.springframework.security.core.authentication;
import org.springframework.security.core.context.securitycontextholder;
import org.springframework.security.core.userdetails.userdetails;
import org.springframework.security.core.userdetails.userdetailsservice;
import org.springframework.security.crypto.password.passwordencoder;
import org.springframework.web.bind.annotation.postmapping;
import org.springframework.web.bind.annotation.requestbody;
import org.springframework.web.bind.annotation.requestmapping;
import org.springframework.web.bind.annotation.restcontroller;
import java.util.hashmap;
import java.util.map;
@restcontroller
@requestmapping("/api/auth")
public class authcontroller {
    @autowired
    private authenticationmanager authenticationmanager;
    @autowired
    private userdetailsservice userdetailsservice;
    @autowired
    private jwttokenutil jwttokenutil;
    @autowired
    private userrepository userrepository;
    @autowired
    private passwordencoder passwordencoder;
    // 登录接口:接收账号密码,返回jwt令牌
    @postmapping("/login")
    public responseentity<map<string, string>> login(@requestbody loginrequest loginrequest) {
        // 1. 执行认证(验证账号密码)
        authentication authentication = authenticationmanager.authenticate(
                new usernamepasswordauthenticationtoken(
                        loginrequest.getusername(),
                        loginrequest.getpassword()
                )
        );
        // 2. 认证通过,将认证信息存入securitycontext
        securitycontextholder.getcontext().setauthentication(authentication);
        // 3. 加载用户信息,生成jwt令牌
        userdetails userdetails = userdetailsservice.loaduserbyusername(loginrequest.getusername());
        string jwt = jwttokenutil.generatetoken(userdetails);
        // 4. 返回令牌和用户信息
        map<string, string> response = new hashmap<>();
        response.put("token", jwt);
        response.put("username", userdetails.getusername());
        response.put("role", userdetails.getauthorities().iterator().next().getauthority().replace("role_", ""));
        return responseentity.ok(response);
    }
    // 注册接口(可选,用于测试)
    @postmapping("/register")
    public responseentity<string> register(@requestbody registerrequest registerrequest) {
        // 检查用户名是否已存在
        if (userrepository.findbyusername(registerrequest.getusername()).ispresent()) {
            return responseentity.badrequest().body("用户名已存在");
        }
        // 创建用户,加密密码
        user user = new user();
        user.setusername(registerrequest.getusername());
        user.setpassword(passwordencoder.encode(registerrequest.getpassword()));
        user.setrole(registerrequest.getrole()); // 如"user"、"admin"
        userrepository.save(user);
        return responseentity.ok("注册成功");
    }
    // 登录请求参数封装
    public static class loginrequest {
        private string username;
        private string password;
        // getter/setter
        public string getusername() { return username; }
        public void setusername(string username) { this.username = username; }
        public string getpassword() { return password; }
        public void setpassword(string password) { this.password = password; }
    }
    // 注册请求参数封装
    public static class registerrequest {
        private string username;
        private string password;
        private string role;
        // getter/setter
        public string getusername() { return username; }
        public void setusername(string username) { this.username = username; }
        public string getpassword() { return password; }
        public void setpassword(string password) { this.password = password; }
        public string getrole() { return role; }
        public void setrole(string role) { this.role = role; }
    }
}

3.7 第七步:配置文件(application.yml)

spring:
  # 数据库配置(h2内存数据库,用于测试)
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.driver
    username: sa
    password: 123456
  # jpa配置
  jpa:
    hibernate:
      ddl-auto: update # 自动创建表结构
    show-sql: true
    properties:
      hibernate:
        format_sql: true
  # h2控制台配置(访问:http://localhost:8080/h2-console)
  h2:
    console:
      enabled: true
      path: /h2-console
# jwt配置
jwt:
  secret: abc1234567890abc1234567890abc1234 # 签名密钥(生产环境需修改,建议至少32位)
  expiration: 3600000 # 过期时间(1小时,单位:毫秒)
# 服务器端口
server:
  port: 8080

3.8 测试验证

  • 启动项目,访问 http://localhost:8080/h2-console,登录h2数据库,插入测试用户:
  •  insert into sys_user (username, password, role) values ('admin', '$2a$10$eixzaybb.rk4fl8x2q7meu6q6d2v4xw6q6d2v4xw6q6d2v4xw6q6', 'admin'), -- 密码:123456 ('user', '$2a$10$eixzaybb.rk4fl8x2q7meu6q6d2v4xw6q6d2v4xw6q6d2v4xw6q6', 'user'); -- 密码:123456
  • 调用注册接口(可选):post http://localhost:8080/api/auth/register,请求体: { "username": "test", "password": "123456", "role": "user" }
  • 调用登录接口:post  http://localhost:8080/api/auth/login,请求体: { "username": "admin", "password": "123456" }响应结果(包含jwt令牌): { "token": "eyjhbgcioijiuzi1niisinr5cci6ikpxvcj9.eyjyb2xlcyi6w3siyxv0ag9yaxr5ijoiuk9mrv9bre1jtij9xswidxnlcm5hbwuioijhzg1pbiisimv4cci6mtcxnti5mtywmcwiawf0ijoxnze1mjg4mdawfq.7z7z7z7z7z7z7z7z7z7z7z7z7z7z7z7z7z7z7", "username": "admin", "role": "admin" }
  • 访问受保护接口(如管理员接口):get  http://localhost:8080/api/admin/test,请求头添加authorization: bearer 令牌,即可正常访问;若不携带令牌或令牌无效,返回401。

四、oauth2.0 + jwt + spring security 实现单点登录(sso)

前面实现了单个微服务的登录与鉴权,而微服务架构中通常有多个服务(如订单服务、用户服务、后台管理服务),需要实现“单点登录”——用户一次登录,即可访问所有授权服务。

核心方案:基于 oauth2.0 授权码模式,搭建独立的授权服务器(统一处理登录、颁发令牌)和资源服务器(各微服务),结合jwt实现无状态单点登录。

4.1 架构设计(核心组件)

  • 授权服务器(authorization server):独立部署,负责用户认证、颁发jwt令牌(access_token、refresh_token)、处理授权请求,是单点登录的核心。
  • 资源服务器(resource server):各个微服务(如订单服务、用户服务),配置oauth2.0和jwt,验证令牌合法性,实现权限管控。
  • 客户端(client):需要接入单点登录的应用(如web后台、app、小程序),提前在授权服务器注册。

4.2 第一步:搭建授权服务器(authorization server)

基于spring security oauth2.0,搭建独立的授权服务器,实现用户登录、授权码颁发、jwt令牌生成。

2.1 导入依赖(maven)

<!-- 新增oauth2.0授权服务器依赖 -->
<dependency>
    <groupid>org.springframework.security.oauth</groupid>
    <artifactid>spring-security-oauth2</artifactid>
    <version>2.3.8.release</version>
</dependency>
<!-- 其他依赖(spring boot web、spring security、jwt、数据库)同上 -->

2.2 配置授权服务器(authorizationserverconfig)

import org.springframework.beans.factory.annotation.autowired;
import org.springframework.context.annotation.configuration;
import org.springframework.security.authentication.authenticationmanager;
import org.springframework.security.crypto.password.passwordencoder;
import org.springframework.security.oauth2.config.annotation.configurers.clientdetailsserviceconfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.authorizationserverconfigureradapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.enableauthorizationserver;
import org.springframework.security.oauth2.config.annotation.web.configurers.authorizationserverendpointsconfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.authorizationserversecurityconfigurer;
import org.springframework.security.oauth2.provider.token.tokenstore;
import org.springframework.security.oauth2.provider.token.store.jwtaccesstokenconverter;
import org.springframework.security.oauth2.provider.token.store.jwttokenstore;
@configuration
@enableauthorizationserver // 启用授权服务器
public class authorizationserverconfig extends authorizationserverconfigureradapter {
    // 客户端id(提前注册,用于客户端身份验证)
    private static final string client_id = "client1";
    // 客户端密钥(加密存储,密码:123456)
    private static final string client_secret = "$2a$10$eixzaybb.rk4fl8x2q7meu6q6d2v4xw6q6d2v4xw6q6d2v4xw6q6";
    // 授权范围
    private static final string scope = "all";
    // 授权码模式
    private static final string grant_type_authorization_code = "authorization_code";
    // 密码模式
    private static final string grant_type_password = "password";
    // 刷新令牌模式
    private static final string grant_type_refresh_token = "refresh_token";
    // 令牌有效期(1小时)
    private static final int access_token_validity_seconds = 3600;
    // 刷新令牌有效期(7天)
    private static final int refresh_token_validity_seconds = 604800;
    @autowired
    private authenticationmanager authenticationmanager;
    @autowired
    private passwordencoder passwordencoder;
    // jwt签名密钥(与资源服务器、jwt工具类一致,生产环境配置在配置中心)
    private static final string jwt_secret = "abc1234567890abc1234567890abc1234";
    // 配置令牌存储(jwt),用于存储和解析jwt令牌
    @bean
    public tokenstore tokenstore() {
        return new jwttokenstore(accesstokenconverter());
    }
    // 配置jwt令牌转换器(设置签名密钥,确保令牌生成和验证的一致性)
    @bean
    public jwtaccesstokenconverter accesstokenconverter() {
        jwtaccesstokenconverter converter = new jwtaccesstokenconverter();
        converter.setsigningkey(jwt_secret); // 与jwt工具类的密钥完全一致
        return converter;
    }
    // 配置客户端信息(客户端在授权服务器注册的核心信息,用于客户端身份校验)
    @override
    public void configure(clientdetailsserviceconfigurer clients) throws exception {
        clients.inmemory()
                // 客户端id(唯一标识,客户端需携带此id请求授权)
                .withclient(client_id)
                // 客户端密钥(加密存储,客户端请求时需携带加密前的密钥进行校验)
                .secret(client_secret)
                // 授权范围,用于限制客户端可访问的资源范围
                .scopes(scope)
                // 支持的授权模式,此处兼容授权码模式(sso核心)、密码模式(内部系统)、刷新令牌模式
                .authorizedgranttypes(grant_type_authorization_code, grant_type_password, grant_type_refresh_token)
                // 访问令牌有效期(1小时,避免令牌长期有效带来的安全风险)
                .accesstokenvalidityseconds(access_token_validity_seconds)
                // 刷新令牌有效期(7天,用户无需频繁登录,提升体验)
                .refreshtokenvalidityseconds(refresh_token_validity_seconds)
                // 回调地址(授权码模式必填,授权服务器颁发授权码后,跳转至此地址传递授权码)
                .redirecturis("http://localhost:8081/callback");
    }
    // 配置授权服务器端点(核心组件关联,确保认证和令牌生成流程正常)
    @override
    public void configure(authorizationserverendpointsconfigurer endpoints) throws exception {
        endpoints
                // 关联认证管理器(用于密码模式,验证用户账号密码合法性)
                .authenticationmanager(authenticationmanager)
                // 关联令牌存储(jwt),用于存储和读取令牌信息
                .tokenstore(tokenstore())
                // 关联令牌转换器(jwt),用于生成和解析jwt令牌
                .accesstokenconverter(accesstokenconverter());
    }
    // 配置授权服务器安全规则(控制授权服务器端点的访问权限)
    @override
    public void configure(authorizationserversecurityconfigurer security) throws exception {
        security
                // 允许所有客户端访问token_key端点(获取jwt签名公钥,用于资源服务器验证令牌)
                .tokenkeyaccess("permitall()")
                // 允许所有客户端访问check_token端点(验证令牌的合法性,资源服务器会调用此端点)
                .checktokenaccess("permitall()")
                // 允许客户端通过表单认证(用于客户端身份校验,简化客户端请求流程)
                .allowformauthenticationforclients();
    }
}

4.3 授权服务器配套配置(完善认证流程)

授权服务器需依赖前文实现的userdetailsservice、passwordencoder等组件,同时补充spring security配置(避免默认登录页面干扰),确保认证流程正常。

4.3.1 授权服务器spring security配置(securityconfig)

import org.springframework.beans.factory.annotation.autowired;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.security.authentication.authenticationmanager;
import org.springframework.security.authentication.dao.daoauthenticationprovider;
import org.springframework.security.config.annotation.authentication.configuration.authenticationconfiguration;
import org.springframework.security.config.annotation.web.builders.httpsecurity;
import org.springframework.security.config.annotation.web.configuration.enablewebsecurity;
import org.springframework.security.config.http.sessioncreationpolicy;
import org.springframework.security.core.userdetails.userdetailsservice;
import org.springframework.security.crypto.bcrypt.bcryptpasswordencoder;
import org.springframework.security.crypto.password.passwordencoder;
import org.springframework.security.web.securityfilterchain;
@configuration
@enablewebsecurity
public class securityconfig {
    @autowired
    private userdetailsservice userdetailsservice;
    // 密码加密器(与前文一致,bcrypt不可逆加密,确保密码安全)
    @bean
    public passwordencoder passwordencoder() {
        return new bcryptpasswordencoder();
    }
    // 认证提供者(关联userdetailsservice和passwordencoder,用于加载用户信息并校验密码)
    @bean
    public daoauthenticationprovider authenticationprovider() {
        daoauthenticationprovider authprovider = new daoauthenticationprovider();
        authprovider.setuserdetailsservice(userdetailsservice);
        authprovider.setpasswordencoder(passwordencoder());
        return authprovider;
    }
    // 认证管理器(核心认证组件,授权服务器密码模式需依赖此组件)
    @bean
    public authenticationmanager authenticationmanager(authenticationconfiguration authconfig) throws exception {
        return authconfig.getauthenticationmanager();
    }
    // 安全过滤器链配置(关闭session,允许授权相关端点匿名访问)
    @bean
    public securityfilterchain filterchain(httpsecurity http) throws exception {
        http
                // 关闭csrf防护(授权服务器无状态,无需csrf)
                .csrf().disable()
                // 关闭session(授权服务器无需存储用户会话,适配微服务无状态架构)
                .sessionmanagement().sessioncreationpolicy(sessioncreationpolicy.stateless).and()
                // 授权规则配置
                .authorizerequests()
                // 授权服务器核心端点允许匿名访问(客户端请求授权、获取令牌需访问这些端点)
                .antmatchers("/oauth/authorize", "/oauth/token", "/oauth/check_token", "/oauth/token_key").permitall()
                // 登录接口、注册接口允许匿名访问(用于用户注册和登录验证)
                .antmatchers("/api/auth/login", "/api/auth/register").permitall()
                // 其他所有请求需认证(避免未授权访问)
                .anyrequest().authenticated();
        // 注册认证提供者
        http.authenticationprovider(authenticationprovider());
        return http.build();
    }
}

4.3.2 授权服务器配置文件(application.yml)

配置端口、数据库、jwt等信息,与前文保持一致,确保组件协同工作:

spring:
  # 数据库配置(h2内存数据库,用于测试,生产环境替换为mysql)
  datasource:
    url: jdbc:h2:mem:authdb
    driver-class-name: org.h2.driver
    username: sa
    password: 123456
  # jpa配置(自动创建表结构,简化测试)
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        format_sql: true
  # h2控制台配置(访问:http://localhost:8080/h2-console,用于插入测试用户)
  h2:
    console:
      enabled: true
      path: /h2-console
# jwt配置(与资源服务器、令牌转换器一致)
jwt:
  secret: abc1234567890abc1234567890abc1234 # 签名密钥,生产环境需修改并配置在配置中心
  expiration: 3600000 # 访问令牌有效期(1小时,与授权服务器配置一致)
# 服务器端口(授权服务器独立部署,端口设为8080,避免与资源服务器冲突)
server:
  port: 8080

4.4 授权服务器测试验证

启动授权服务器,完成以下测试,确保授权服务器可正常颁发授权码和jwt令牌:

  • 启动项目,访问h2控制台(http://localhost:8080/h2-console),登录后插入测试用户(与前文一致):
  • insert into sys_user (username, password, role) values ('admin', '$2a$10$eixzaybb.rk4fl8x2q7meu6q6d2v4xw6q6d2v4xw6q6d2v4xw6q6', 'admin'), -- 密码:123456 ('user', '$2a$10$eixzaybb.rk4fl8x2q7meu6q6d2v4xw6q6d2v4xw6q6d2v4xw6q6', 'user'); -- 密码:123456
  • 请求授权码(授权码模式):访问以下地址,引导用户登录并授权,获取授权码(code): http://localhost:8080/oauth/authorize?client_id=client1&response_type=code&redirect_uri=http://localhost:8081/callback&scope=all
  • 访问后会跳转至登录页面,输入用户名(admin)和密码(123456);
  • 登录成功后,会跳转至回调地址(http://localhost:8081/callback),地址栏会携带授权码(code参数),例如:http://localhost:8081/callback?code=abc123(code为临时凭证,5分钟内有效)。
  • 通过授权码获取访问令牌(access_token):使用postman发送post请求,地址:http://localhost:8080/oauth/token,参数如下:
    • 请求头:添加authorization: basic y2xpzw50mtoxmjm0nty=(client1:123456的base64编码);
    • 请求体(form-data): grant_type=authorization_code、code=步骤2获取的授权码、redirect_uri=http://localhost:8081/callback、scope=all;
    • 响应结果:会返回access_token(jwt令牌)、refresh_token、token_type等信息,示例: { "access_token": "eyjhbgcioijiuzi1niisinr5cci6ikpxvcj9.eyj1c2vyx25hbwuioijhzg1pbiisinnjb3blijoidxnlciisimv4cci6mtcxnti5mtywmcwiawf0ijoxnze1mjg4mdawfq.7z7z7z7z7z7z7z7z7z7z7z7z7z7z7z7z7z7z7", "token_type": "bearer", "refresh_token": "eyjhbgcioijiuzi1niisinr5cci6ikpxvcj9.eyj1c2vyx25hbwuioijhzg1pbiisinnjb3blijoidxnlciisimf0asi6imfiyzeymyisimv4cci6mtcxntg5mjgwmcwiawf0ijoxnze1mjg4mdawfq.8z8z8z8z8z8z8z8z8z8z8z8z8z8z8z8z8z8z8", "expires_in": 3599, "scope": "all" }

五、第二步:搭建资源服务器(resource server)

资源服务器即各个微服务(如订单服务、用户服务),需配置oauth2.0和jwt,实现令牌验证和权限管控,确保只有携带合法jwt令牌的请求才能访问受保护资源。

5.1 导入资源服务器依赖(maven)

<!-- spring boot 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>
<!-- oauth2.0资源服务器依赖 -->
<dependency>
    <groupid>org.springframework.security.oauth</groupid>
    <artifactid>spring-security-oauth2</artifactid>
    <version>2.3.8.release</version>
</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>

5.2 配置资源服务器(resourceserverconfig)

核心配置:关联jwt令牌转换器和令牌存储,配置资源访问权限,验证令牌合法性。

import org.springframework.context.annotation.configuration;
import org.springframework.security.oauth2.config.annotation.web.configuration.enableresourceserver;
import org.springframework.security.oauth2.config.annotation.web.configuration.resourceserverconfigureradapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.resourceserversecurityconfigurer;
import org.springframework.security.oauth2.provider.token.tokenstore;
import org.springframework.security.oauth2.provider.token.store.jwtaccesstokenconverter;
import org.springframework.security.oauth2.provider.token.store.jwttokenstore;
@configuration
@enableresourceserver // 启用资源服务器
public class resourceserverconfig extends resourceserverconfigureradapter {
    // 资源id(唯一标识,与授权服务器客户端配置的资源范围对应)
    private static final string resource_id = "all";
    // jwt签名密钥(与授权服务器完全一致,否则无法验证令牌)
    private static final string jwt_secret = "abc1234567890abc1234567890abc1234";
    // 配置令牌存储(jwt),与授权服务器一致
    @bean
    public tokenstore tokenstore() {
        return new jwttokenstore(accesstokenconverter());
    }
    // 配置jwt令牌转换器(设置签名密钥,用于验证令牌签名)
    @bean
    public jwtaccesstokenconverter accesstokenconverter() {
        jwtaccesstokenconverter converter = new jwtaccesstokenconverter();
        converter.setsigningkey(jwt_secret);
        return converter;
    }
    // 配置资源服务器核心信息(令牌存储、资源id)
    @override
    public void configure(resourceserversecurityconfigurer resources) throws exception {
        resources
                // 关联资源id(与授权服务器客户端的scope对应)
                .resourceid(resource_id)
                // 关联令牌存储(用于验证令牌合法性)
                .tokenstore(tokenstore())
                // 令牌验证失败时,返回401未授权响应
                .stateless(true);
    }
    // 配置资源访问权限规则(根据用户角色控制资源访问)
    @override
    public void configure(org.springframework.security.config.annotation.web.builders.httpsecurity http) throws exception {
        http
                // 关闭csrf防护(资源服务器无状态,无需csrf)
                .csrf().disable()
                // 关闭session(适配微服务无状态架构)
                .sessionmanagement().sessioncreationpolicy(org.springframework.security.config.http.sessioncreationpolicy.stateless).and()
                // 授权规则配置
                .authorizerequests()
                // 公开接口允许匿名访问(如健康检查接口)
                .antmatchers("/health/**").permitall()
                // 管理员接口仅允许admin角色访问
                .antmatchers("/api/admin/**").hasrole("admin")
                // 普通用户接口允许user或admin角色访问
                .antmatchers("/api/user/**").hasanyrole("user", "admin")
                // 其他所有资源需携带合法令牌才能访问
                .anyrequest().authenticated();
    }
}

5.3 资源服务器配置文件(application.yml)

配置端口(与授权服务器不同,避免端口冲突)、jwt等信息:

# 服务器端口(资源服务器独立部署,设为8081,与授权服务器8080区分)
server:
  port: 8081
# jwt配置(与授权服务器完全一致)
jwt:
  secret: abc1234567890abc1234567890abc1234
  expiration: 3600000
# oauth2.0资源服务器配置
security:
  oauth2:
    resource:
      # 令牌验证端点(授权服务器的check_token端点,用于验证令牌合法性)
      token-info-uri: http://localhost:8080/oauth/check_token
      # 资源id(与资源服务器配置的resource_id一致)
      id: all

5.4 实现资源服务器测试接口

创建测试接口,用于验证单点登录和权限管控是否生效:

import org.springframework.security.core.context.securitycontextholder;
import org.springframework.security.core.userdetails.userdetails;
import org.springframework.web.bind.annotation.getmapping;
import org.springframework.web.bind.annotation.requestmapping;
import org.springframework.web.bind.annotation.restcontroller;
@restcontroller
@requestmapping("/api")
public class testcontroller {
    // 普通用户接口(user、admin角色可访问)
    @getmapping("/user/test")
    public string usertest() {
        // 获取当前登录用户信息
        userdetails userdetails = (userdetails) securitycontextholder.getcontext().getauthentication().getprincipal();
        return "普通用户接口访问成功!当前登录用户:" + userdetails.getusername() + ",角色:" + userdetails.getauthorities();
    }
    // 管理员接口(仅admin角色可访问)
    @getmapping("/admin/test")
    public string admintest() {
        userdetails userdetails = (userdetails) securitycontextholder.getcontext().getauthentication().getprincipal();
        return "管理员接口访问成功!当前登录用户:" + userdetails.getusername() + ",角色:" + userdetails.getauthorities();
    }
    // 回调接口(用于接收授权服务器颁发的授权码,仅授权码模式使用)
    @getmapping("/callback")
    public string callback(string code) {
        // 此处可接收授权码,后续可结合客户端逻辑获取access_token(实际场景中由客户端处理)
        return "授权码接收成功!code:" + code;
    }
}

六、第三步:单点登录(sso)完整测试

启动授权服务器(8080端口)和资源服务器(8081端口),完成以下测试,验证单点登录效果(一次登录,多服务访问):

6.1 测试流程(授权码模式,sso核心流程)

  • 获取授权码:访问http://localhost:8080/oauth/authorize?client_id=client1&response_type=code&redirect_uri=http://localhost:8081/callback&scope=all,登录admin用户(密码123456),获取回调地址中的code;
  • 获取access_token:通过postman发送post请求到http://localhost:8080/oauth/token,携带code、client_id、client_secret等参数,获取access_token(jwt令牌);
    • 访问资源服务器接口:
    • 访问普通用户接口:http://localhost:8081/api/user/test,请求头添加authorization: bearer 第一步获取的access_token,可正常访问;
    • 访问管理员接口:http://localhost:8081/api/admin/test,请求头添加相同的access_token,可正常访问(admin角色拥有权限);
  • 测试单点登录:新增另一个资源服务器(如订单服务,端口8082),配置与8081端口资源服务器一致,使用相同的access_token访问其受保护接口,无需重新登录即可正常访问,实现单点登录。

6.2 常见问题排查

  • 令牌验证失败:检查资源服务器与授权服务器的jwt_secret是否一致,access_token是否过期;
  • 权限不足(403):检查用户角色是否与接口要求的角色匹配,user实体类中角色转换是否添加role_前缀;
  • 授权码无效:授权码仅5分钟有效,且一次使用后失效,需重新获取授权码。

七、生产环境适配

上述实操为测试环境配置,生产环境需进行以下优化,提升安全性和可维护性:

7.1 安全优化

  • 密钥管理:jwt_secret、client_secret等敏感信息,不硬编码,配置在nacos、apollo等配置中心,定期更换;
  • 令牌安全:缩短access_token有效期(如30分钟),refresh_token添加黑名单机制(结合redis),支持主动注销令牌;
  • 加密传输:所有接口使用https协议,避免令牌在传输过程中被窃取;
  • 权限细化:基于资源的细粒度权限管控(如用户只能访问自己的订单),结合spring security的方法级权限注解(@preauthorize)。

7.2 架构优化

  • 授权服务器集群部署:避免单点故障,使用redis共享令牌黑名单;
  • 资源服务器统一配置:将oauth2.0和jwt配置抽取为公共依赖,所有微服务引入,减少重复开发;
  • 日志与监控:添加令牌生成、验证、失效的日志记录,监控令牌使用情况,及时发现异常访问。

八、总结

  • 授权服务器:独立部署,负责用户认证、颁发授权码和jwt令牌,统一管理客户端和用户信息;
  • 资源服务器:各个微服务,配置令牌验证规则,实现权限管控,仅允许携带合法令牌的请求访问;
  • 单点登录:用户通过授权服务器一次登录,获取jwt令牌,即可访问所有授权的资源服务器,无需重复登录。

到此这篇关于jwt + spring security / oauth2.0:微服务统一登录、鉴权、单点登录全解析的文章就介绍到这了,更多相关spring security 微服务统一登录内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

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

发表评论

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