本文只讲密码模式,因为入门简单,让大家先跑通。
下一篇:进阶:授权码模式sringcloud授权码模式------单点登录
原因:SSO-springcloud-alibaba 搜了一些,都不完整,现在记录一下自己整理的完整源码,分享出来
解决:
1.项目结构:
一个项目(alibaba_sso),2个模块(<module>auth-server</module> <module>resource-server</module>)。



2.直接放源码:
项目pom(alibaba_sso.pom)
java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.dp</groupId>
<artifactId>alibaba_sso</artifactId>
<version>1.0.0</version>
<name>alibaba_sso</name>
<description>alibaba_sso</description>
<packaging>pom</packaging>
<modules>
<module>auth-server</module>
<module>resource-server</module>
</modules>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2021.0.9</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.6.0</spring-cloud-alibaba.version>
<spring-security-oauth2.version>2.5.12</spring-security-oauth2.version>
<jjwt.version>0.11.5</jjwt.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Security OAuth2 -->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>${spring-security-oauth2.version}</version>
</dependency>
<!-- JJWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
auth_server:
pom
java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.dp</groupId>
<artifactId>alibaba_sso</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>auth-server</artifactId>
<name>auth-server</name>
<description>auth-server</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- 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>
<!-- Spring Security OAuth2 -->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
<!-- JJWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
AuthServerApplication.java
java
package com.dp.authserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient // 注册到 Nacos
public class AuthServerApplication {
public static void main(String[] args) {
SpringApplication.run(AuthServerApplication.class, args);
}
}
AuthServerConfig.java
java
package com.dp.authserver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
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.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;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
/**
* 认证服务器配置
* ============================================
* 认证方式:OAuth2 密码模式(Password Grant)
*
* 流程说明:
* 1. 客户端使用用户名/密码向 /oauth/token 请求 token
* 2. 认证服务器验证用户名密码,生成 JWT Token 返回
* 3. 客户端携带 Token 访问资源服务器
* ============================================
*/
@Configuration
@EnableWebSecurity
@EnableAuthorizationServer // 启用 OAuth2 认证服务器
public class AuthServerConfig {
/**
* 密码编码器
* 使用 BCrypt 加密,存储时自动加盐
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 用户认证管理器
* 负责验证用户名和密码
*/
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder())
.and()
.build();
}
/**
* 用户详情服务
* 定义测试用户:admin/123456
* 密码使用 BCrypt 加密
*/
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// 密码: 123456 的 BCrypt 加密结果
manager.createUser(User.withUsername("admin")
.password("$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi")
.roles("USER", "ADMIN")
.build());
manager.createUser(User.withUsername("user")
.password("$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi")
.roles("USER")
.build());
return manager;
}
/**
* JWT Token 存储
* 使用 JWT 格式存储 Token,无需 Redis
*/
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* JWT Token 转换器
* 配置签名密钥,用于生成和验证 JWT
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
// 签名密钥(生产环境请使用更强的密钥)
converter.setSigningKey("sso-secret-key-2024");
return converter;
}
/**
* Spring Security 配置
* 允许所有用户访问 /oauth/token 端点
*/
@Configuration
public static class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/oauth/token", "/oauth/authorize").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic();
}
}
/**
* OAuth2 授权服务器配置
* 定义客户端信息和 Token 端点
*/
@Configuration
public static class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final TokenStore tokenStore;
private final JwtAccessTokenConverter jwtAccessTokenConverter;
public AuthorizationServerConfig(
AuthenticationManager authenticationManager,
TokenStore tokenStore,
JwtAccessTokenConverter jwtAccessTokenConverter) {
this.authenticationManager = authenticationManager;
this.tokenStore = tokenStore;
this.jwtAccessTokenConverter = jwtAccessTokenConverter;
}
/**
* 配置客户端信息
* 定义哪些应用可以访问认证服务器
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 使用 BCrypt 编码客户端密码
PasswordEncoder encoder = new BCryptPasswordEncoder();
String encodedClientSecret = encoder.encode("gateway-secret");
clients.inMemory()
.withClient("gateway-client")
.secret(encodedClientSecret) // 使用 BCrypt 编码的密码
.authorizedGrantTypes(
"password",
"refresh_token"
)
.scopes("all", "read", "write")
.accessTokenValiditySeconds(3600)
.refreshTokenValiditySeconds(86400);
}
/**
* 配置 Token 端点
* 设置认证管理器和 Token 存储
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager) // 密码模式需要
.tokenStore(tokenStore) // Token 存储
.accessTokenConverter(jwtAccessTokenConverter); // JWT 转换器
}
/**
* 配置 Token 端点的安全策略
* 允许表单认证和客户端认证
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security
.tokenKeyAccess("permitAll()") // 允许所有访问 /oauth/token_key
.checkTokenAccess("isAuthenticated()") // 检查 Token 需要认证
.allowFormAuthenticationForClients(); // 允许表单认证
}
}
}
applicaiton.yml
java
server:
port: 9001
spring:
application:
name: auth-server
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: public
logging:
level:
org.springframework.security: DEBUG
org.springframework.security.oauth2: DEBUG
resource-server:
pom.xml
java
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.dp</groupId>
<artifactId>alibaba_sso</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>resource-server</artifactId>
<name>resource-server</name>
<description>resource-server</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security OAuth2 Resource Server -->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
<!-- JJWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
ResourceServerApplication.java
java
package com.dp.resourceserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ResourceServerApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceServerApplication.class, args);
}
}
ResourceServerConfig.java
java
package com.dp.resourceserver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.DefaultTokenServices;
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;
/**
* 资源服务器配置
* ============================================
* 验证流程:
* 1. 使用与认证服务器相同的 JWT 签名密钥
* 2. 验证 Token 签名和有效期
*
* 关键:setSigningKey() 用于设置签名密钥(验证时使用)
* ============================================
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
/**
* JWT 签名密钥(必须与认证服务器完全一致)
*/
private static final String JWT_SIGNING_KEY = "sso-secret-key-2024";
/**
* Token 存储 - 使用 JWT 格式
*/
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* JWT Token 转换器 - 配置签名密钥
*
* 注意:使用 setSigningKey() 而不是 setVerificationKey()
* 在 Spring Security OAuth2 中,JwtAccessTokenConverter 使用 setSigningKey() 同时处理签名和验证
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
// 设置签名密钥(必须与认证服务器相同)
converter.setSigningKey(JWT_SIGNING_KEY);
return converter;
}
/**
* Token 服务
*/
@Bean
public DefaultTokenServices tokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
services.setTokenStore(tokenStore());
return services;
}
/**
* 配置资源ID
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("resource-server")
.stateless(true)
.tokenServices(tokenServices());
}
/**
* 配置接口访问权限
*/
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// 公开接口
.antMatchers("/api/public/**").permitAll()
// 用户接口需要认证
.antMatchers("/api/user/**").authenticated()
// 管理员接口需要 ADMIN 角色
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.csrf().disable();
}
}
UserController.java
java
package com.dp.resourceserver.controller;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class UserController {
/**
* 公开接口 - 不需要 Token
*/
@GetMapping("/public/health")
public Map<String, Object> health() {
Map<String, Object> result = new HashMap<>();
result.put("status", "UP");
result.put("service", "resource-server");
result.put("message", "Service is running");
return result;
}
/**
* 用户信息接口 - 需要 Token
* 通过 Principal 获取当前登录用户
*/
@GetMapping("/user/info")
public Map<String, Object> getUserInfo(Principal principal) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Map<String, Object> result = new HashMap<>();
result.put("username", principal.getName());
result.put("authenticated", authentication.isAuthenticated());
result.put("authorities", authentication.getAuthorities());
result.put("service", "resource-server");
return result;
}
/**
* 管理员接口 - 需要 ADMIN 角色
*/
@GetMapping("/admin/dashboard")
public Map<String, Object> adminDashboard() {
Map<String, Object> result = new HashMap<>();
result.put("data", "Admin Dashboard Data");
result.put("service", "resource-server");
return result;
}
}
application.yml
java
server:
port: 9002
spring:
application:
name: resource-server
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: public
# JWT 签名密钥(必须与认证服务器一致)
jwt:
secret: sso-secret-key-2024
logging:
level:
org.springframework.security: DEBUG
3.启动
3.1 启动 nacos,我的版本是3.0.2

3.2 启动 auth-server
3.3 启动 resource-server
4.测试:(用的postman)
4.1 获取token:
http://localhost:9001/oauth/token
post
header:
Authorization:Basic Z2F0ZXdheS1jbGllbnQ6Z2F0ZXdheS1zZWNyZXQ=
body:
grant_type:password
username:admin
password:123456
scope:all
返回:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NzQ4Njg0MzEsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiJsVGNVblBSaVg4WENqX3Vva2hXeERFZHVlcXMiLCJjbGllbnRfaWQiOiJnYXRld2F5LWNsaWVudCIsInNjb3BlIjpbImFsbCJdfQ.cQ_jzo7nwuTyinx5Xt4qQt2ZCcKo5_K0s-JxPnti3G8",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiJsVGNVblBSaVg4WENqX3Vva2hXeERFZHVlcXMiLCJleHAiOjE3NzQ5NTEyMzEsImF1dGhvcml0aWVzIjpbIlJPTEVfQURNSU4iLCJST0xFX1VTRVIiXSwianRpIjoiNVpMVlRSTmFwcVEtX1ZzRnlNZkp0Uk90NnlVIiwiY2xpZW50X2lkIjoiZ2F0ZXdheS1jbGllbnQifQ.x3_Mb0gYR8YDWlBc8hE2oEKzECx6ed7_YJc8v8sSgKE",
"expires_in": 3599,
"scope": "all",
"jti": "lTcUnPRiX8XCj_uokhWxDEdueqs"
}

4.2 测试数据:
http://localhost:9002/api/user/info
header:
Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NzQ4NjQ0NDAsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiIwbVJDVVkzWjNuTTFiamZfSmxIaVN2ek1JWDQiLCJjbGllbnRfaWQiOiJnYXRld2F5LWNsaWVudCIsInNjb3BlIjpbImFsbCJdfQ.tlveU1Br_PsPXfZIKHYB6pXlCpkEj9cS7gc93KUGQXU
返回:
{
"authenticated": true,
"service": "resource-server",
"authorities": [
{
"authority": "ROLE_ADMIN"
},
{
"authority": "ROLE_USER"
}
],
"username": "admin"
}

4.3 http://localhost:9002/api/admin/dashboard
header 一样:权限不一样的测试

4.4 不用执行,备份收藏的postman的测试方式






原理等大家实现后,看注释就可以,先跑通。代码放deepseek也可以