创建父工程 spring-security
pom.xml
<?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>
<groupId>com.elew</groupId>
<artifactId>spring-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>spring-security</name>
<description>spring-security</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.7.6</spring-boot.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.5.2</version>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>spring-auth-server</module>
</modules>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
创建模块工程 spring-auth-server
支持授权码模式、简单模式、客户端模式、自定义短信模式、刷新token
密码模式
- pom.xml 引入依赖
<dependencies>
<!-- Spring Boot 依赖 -->
<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 依赖 -->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
<!-- JWT 依赖 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.1.RELEASE</version>
</dependency>
<!-- MyBatis-Plus 依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok 依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
项目配置文件
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://192.168.3.101:3306/spring_security?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.springauthserver.entity
configuration:
map-underscore-to-camel-case: true
jwt:
signingKey: elewkey
1.服务授权配置类 AuthServerConfig.java
package com.elew.springauthserver.conf;
import com.elew.springauthserver.service.OAuthClientDetailsService;
import com.elew.springauthserver.sms.SmsCodeAuthorizationGrantType;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.token.*;
import java.util.*;
@Configuration
@EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
// 注入 AuthenticationManager,用于验证用户的身份信息
private final AuthenticationManager authenticationManager;
// 注入 UserDetailsService,用于加载用户的详细信息
private final UserDetailsService userDetailsService;
// 注入 OAuthClientDetailsService,用于加载客户端的详细信息
private final OAuthClientDetailsService oAuthClientDetailsService;
private final AuthorizationServerTokenServices tokenService;
private final TokenStore tokenStore;
public AuthServerConfig(AuthenticationManager authenticationManager, UserDetailsService userDetailsService, OAuthClientDetailsService oAuthClientDetailsService, AuthorizationServerTokenServices tokenService, TokenStore tokenStore) {
this.authenticationManager = authenticationManager;
this.userDetailsService = userDetailsService;
this.oAuthClientDetailsService = oAuthClientDetailsService;
this.tokenService = tokenService;
this.tokenStore = tokenStore;
}
// 配置授权服务器的安全规则,如令牌端点的访问权限
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer
.tokenKeyAccess("permitAll()") // 允许所有人访问令牌密钥端点
.checkTokenAccess("permitAll()") // 只有认证用户才能访问令牌校验端点
.allowFormAuthenticationForClients(); // 允许客户端使用表单进行认证
}
/**
* 配置自定义的 TokenGranter,用于支持短信验证码登录
*
* @param endpoints
* @throws Exception
*/
private TokenGranter tokenGranter(final AuthorizationServerEndpointsConfigurer endpoints) {
List<TokenGranter> granters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter()));
granters.add(new SmsCodeAuthorizationGrantType(
tokenService,
endpoints.getClientDetailsService(),
endpoints.getOAuth2RequestFactory(),
userDetailsService
));
return new CompositeTokenGranter(granters);
}
// 配置客户端详情服务,包括客户端 ID、密钥、授权类型、作用域等。
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientId -> oAuthClientDetailsService.loadClientByClientId(clientId));
}
// 配置授权服务器的端点,如授权端点、令牌端点,以及令牌的存储和管理
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager) // 密码模式需要
.userDetailsService(userDetailsService)
.tokenStore(tokenStore)
.tokenServices(tokenService)
.tokenGranter(tokenGranter(endpoints))
;
}
}
2. Token配置类 TokenConfig.java
package com.elew.springauthserver.conf;
import com.elew.springauthserver.entity.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import java.util.*;
@Configuration
public class TokenConfig {
@Value("${jwt.signingKey}")
private String signingKey;
/**
* 配置 TokenServices
* // 标记:@Primary 默认自动注入了 defaultAuthorizationServerTokenServices:由 org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration 类中的 defaultAuthorizationServerTokenServices 方法定义
* @return
*/
@Bean
@Primary
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
// 设置 TokenStore
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenEnhancer(tokenEnhancerChain());
// 可根据需求设置其他属性
return tokenServices;
}
/**
* 配置 JWT 令牌存储
* @return TokenStore 实例
*/
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* 配置 JWT 访问令牌转换器
* @return JwtAccessTokenConverter 实例
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
// 设置签名密钥,实际应用中应使用更安全的方式
converter.setSigningKey(signingKey);
return converter;
}
/**
* 配置令牌增强链
* @return TokenEnhancerChain 实例
*/
@Bean
public TokenEnhancerChain tokenEnhancerChain() {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter()));
return tokenEnhancerChain;
}
/**
* 自定义令牌增强器,可添加额外的声明
* @return TokenEnhancer 实例
*/
@Bean
public TokenEnhancer tokenEnhancer() {
return (accessToken, authentication) -> {
User user = (User) authentication.getPrincipal();
// 获取用户权限信息
List<String> authorities = new ArrayList<>();
authentication.getAuthorities().forEach(grantedAuthority -> {
authorities.add(grantedAuthority.getAuthority());
});
// 添加用户权限信息到 JWT 载荷
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("authorities", authorities);
additionalInfo.put("user_id", user.getId());
additionalInfo.put("username", user.getUsername());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
};
}
}
3. 请求安全规则配置 SecurityConfig.java
package com.elew.springauthserver.conf;
import com.elew.springauthserver.service.UserDetailsServiceImpl;
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.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.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(auth -> auth.antMatchers("/oauth/**").permitAll() // 允许访问登录页面
.anyRequest().authenticated() // 其他请求需要认证
).formLogin().and().httpBasic();
}
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance();
}
}
4. 自定义授权类型 SmsCodeAuthorizationGrantType.java
自定义授权 继承 AbstractTokenGranter 实现方法 getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest)
package com.elew.springauthserver.sms;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.token.TokenService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.Map;
public class SmsCodeAuthorizationGrantType extends AbstractTokenGranter {
private static final String GRANT_TYPE = "sms_code";
private final UserDetailsService userDetailsService;
public SmsCodeAuthorizationGrantType(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, UserDetailsService userDetailsService) {
super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.userDetailsService = userDetailsService;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = tokenRequest.getRequestParameters();
String mobile = parameters.get("mobile");
String code = parameters.get("code");
// 验证短信验证码
if (!validateSmsCode(mobile, code)) {
throw new InvalidGrantException("Invalid sms code");
}
// 加载用户信息
UserDetails user = userDetailsService.loadUserByUsername(mobile);
// 创建认证信息
UsernamePasswordAuthenticationToken userAuth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
userAuth.setDetails(parameters);
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
private boolean validateSmsCode(String mobile, String code) {
// 实现短信验证码验证逻辑
// 这里简单返回 true,实际应用中需要验证验证码是否正确
return true;
}
}
5. 实体类
package com.elew.springauthserver.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("oauth_client_details")
public class OAuthClientDetails {
private String clientId;
private String resourceIds;
private String clientSecret;
private String scope;
private String authorizedGrantTypes;
private String webServerRedirectUri;
private String authorities;
private Integer accessTokenValidity;
private Integer refreshTokenValidity;
private String additionalInformation;
private String autoapprove;
}
package com.elew.springauthserver.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
@Data
@TableName("permissions")
public class Permission {
private Long id;
private String permissionName;
private String permissionCode;
private String description;
private String resourceType;
private String resourceUrl;
private Integer status;
private Date createdAt;
private Date updatedAt;
}
package com.elew.springauthserver.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.*;
@Data
@TableName("users")
public class User implements UserDetails {
private Long id;
private String username;
private String password;
private String email;
private String phone;
private String realName;
private String avatar;
private Integer status;
private Date createdAt;
private Date updatedAt;
@TableField(exist = false)
private Set<Role> roles;
// 标记为数据库不存在的字段
@TableField(exist = false)
private boolean accountNonExpired = true;
@TableField(exist = false)
private boolean accountNonLocked = true;
@TableField(exist = false)
private boolean credentialsNonExpired = true;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
if (roles != null) {
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleCode()));
if (role.getPermissions() != null) {
for (Permission permission : role.getPermissions()) {
authorities.add(new SimpleGrantedAuthority(permission.getPermissionCode()));
}
}
}
}
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return status == 1;
}
}
package com.elew.springauthserver.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
import java.util.Set;
@Data
@TableName("roles")
public class Role {
private Long id;
private String roleName;
private String roleCode;
private String description;
private Integer status;
private Date createdAt;
private Date updatedAt;
@TableField(exist = false)
private Set<Permission> permissions;
}
6. mapper
package com.elew.springauthserver.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.elew.springauthserver.entity.OAuthClientDetails;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OAuthClientDetailsMapper extends BaseMapper<OAuthClientDetails> {
}
package com.elew.springauthserver.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.elew.springauthserver.entity.Permission;
public interface PermissionMapper extends BaseMapper<Permission> {
}
package com.elew.springauthserver.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.elew.springauthserver.entity.Permission;
import com.elew.springauthserver.entity.Role;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Set;
@Mapper
public interface RoleMapper extends BaseMapper<Role> {
Set<Permission> selectPermissionsByRoleId(@Param("roleId") Long roleId);
}
package com.elew.springauthserver.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.elew.springauthserver.entity.Role;
import com.elew.springauthserver.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Set;
@Mapper
public interface UserMapper extends BaseMapper<User> {
Set<Role> selectRolesByUserId(@Param("userId") Long userId);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.elew.springauthserver.mapper.RoleMapper">
<select id="selectPermissionsByRoleId" resultType="com.elew.springauthserver.entity.Permission">
SELECT p.* FROM permissions p
JOIN role_permissions rp ON p.id = rp.permission_id
WHERE rp.role_id = #{roleId}
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.elew.springauthserver.mapper.UserMapper">
<select id="selectRolesByUserId" resultType="com.elew.springauthserver.entity.Role">
SELECT r.* FROM roles r JOIN user_roles ur ON r.id = ur.role_id
WHERE ur.user_id = #{userId}
</select>
</mapper>
7. Service
package com.elew.springauthserver.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.elew.springauthserver.entity.OAuthClientDetails;
import com.elew.springauthserver.mapper.OAuthClientDetailsMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.stream.Collectors;
@Service
public class OAuthClientDetailsService implements ClientDetailsService {
@Autowired
private OAuthClientDetailsMapper oAuthClientDetailsMapper;
/**
* Load client details by client id.
* @param clientId The client id.
* @return
* @throws ClientRegistrationException
*/
@Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
QueryWrapper<OAuthClientDetails> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("client_id", clientId);
OAuthClientDetails clientDetails = oAuthClientDetailsMapper.selectOne(queryWrapper);
if (clientDetails == null) {
throw new ClientRegistrationException("Client not found");
}
BaseClientDetails baseClientDetails = new BaseClientDetails();
baseClientDetails.setResourceIds(Arrays.stream(clientDetails.getResourceIds().split(",")).map(String::trim).collect(Collectors.toList()));
baseClientDetails.setClientId(clientDetails.getClientId());
baseClientDetails.setClientSecret(clientDetails.getClientSecret());
baseClientDetails.setScope(Arrays.asList(clientDetails.getScope().split(",")));
baseClientDetails.setAuthorizedGrantTypes(Arrays.asList(clientDetails.getAuthorizedGrantTypes().split(",")));
baseClientDetails.setRegisteredRedirectUri(
clientDetails.getWebServerRedirectUri() != null ?
java.util.Collections.singleton(clientDetails.getWebServerRedirectUri()) :
java.util.Collections.emptySet()
);
baseClientDetails.setAuthorities(
Arrays.stream(clientDetails.getAuthorities().split(","))
.map(auth -> new org.springframework.security.core.authority.SimpleGrantedAuthority(auth)).collect(Collectors.toList())
);
baseClientDetails.setAccessTokenValiditySeconds(clientDetails.getAccessTokenValidity());
baseClientDetails.setRefreshTokenValiditySeconds(clientDetails.getRefreshTokenValidity());
return baseClientDetails;
}
}
package com.elew.springauthserver.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.elew.springauthserver.entity.Permission;
import com.elew.springauthserver.entity.Role;
import com.elew.springauthserver.entity.User;
import com.elew.springauthserver.mapper.RoleMapper;
import com.elew.springauthserver.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserMapper userMapper;
private final RoleMapper roleMapper;
public UserDetailsServiceImpl(UserMapper userMapper, RoleMapper roleMapper) {
this.userMapper = userMapper;
this.roleMapper = roleMapper;
}
/**
* Load user details by username.
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("username", username);
User user = userMapper.selectOne(userQueryWrapper);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
Set<Role> roles = userMapper.selectRolesByUserId(user.getId());
user.setRoles(roles);
for (Role role : roles) {
Set<Permission> permissions = roleMapper.selectPermissionsByRoleId(role.getId());
role.setPermissions(permissions);
}
return user;
}
}
8. 主工程类
package com.elew.springauthserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AuthServer {
public static void main(String[] args) {
SpringApplication.run(AuthServer.class, args);
}
}
9. 数据库脚本
/*
Navicat Premium Data Transfer
Source Server : 192.168.3.101
Source Server Type : MySQL
Source Server Version : 80042
Source Host : 192.168.3.101:3306
Source Schema : spring_security
Target Server Type : MySQL
Target Server Version : 80042
File Encoding : 65001
Date: 10/07/2025 18:35:35
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(256) NOT NULL COMMENT '客户端唯一标识',
`resource_ids` varchar(256) DEFAULT NULL COMMENT '客户端可访问的资源 ID 列表',
`client_secret` varchar(256) DEFAULT NULL COMMENT '客户端密钥',
`scope` varchar(256) DEFAULT NULL COMMENT '客户端权限范围',
`authorized_grant_types` varchar(256) DEFAULT NULL COMMENT '客户端支持的授权类型',
`web_server_redirect_uri` varchar(256) DEFAULT NULL COMMENT '授权回调地址',
`authorities` varchar(256) DEFAULT NULL COMMENT '客户端拥有的权限',
`access_token_validity` int DEFAULT NULL COMMENT '访问令牌有效期(秒)',
`refresh_token_validity` int DEFAULT NULL COMMENT '刷新令牌有效期(秒)',
`additional_information` text COMMENT '客户端额外信息,存储 JSON 数据',
`autoapprove` varchar(256) DEFAULT NULL COMMENT '是否自动授权',
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='OAuth2 客户端信息表';
-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
BEGIN;
INSERT INTO `oauth_client_details` VALUES ('app1', 'user,order', 'secret', 'read_profile,write_order', 'authorization_code,client_credentials,password,sms_code,refresh_token', 'http://localhost:8080/callback', 'ROLE_MOBILE_CLIENT', 3600, 2592000, '{\"device_type\": \"android\", \"app_version\": \"1.2.3\"}', 'false');
COMMIT;
-- ----------------------------
-- Table structure for permissions
-- ----------------------------
DROP TABLE IF EXISTS `permissions`;
CREATE TABLE `permissions` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '权限 ID',
`permission_name` varchar(50) NOT NULL COMMENT '权限名称',
`permission_code` varchar(50) NOT NULL COMMENT '权限编码',
`description` varchar(255) DEFAULT NULL COMMENT '权限描述',
`resource_type` varchar(20) DEFAULT NULL COMMENT '资源类型,如 MENU(菜单)、BUTTON(按钮)',
`resource_url` varchar(255) DEFAULT NULL COMMENT '资源 URL',
`status` tinyint DEFAULT '1' COMMENT '权限状态,1 正常,0 禁用',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `permission_name` (`permission_name`),
UNIQUE KEY `permission_code` (`permission_code`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='权限表';
-- ----------------------------
-- Records of permissions
-- ----------------------------
BEGIN;
INSERT INTO `permissions` VALUES (1, '用户管理', 'USER_MANAGE', '管理用户信息', 'MENU', '/admin/users', 1, '2025-07-10 03:08:06', '2025-07-10 03:08:06');
INSERT INTO `permissions` VALUES (2, '角色管理', 'ROLE_MANAGE', '管理角色信息', 'MENU', '/admin/roles', 1, '2025-07-10 03:08:06', '2025-07-10 03:08:06');
INSERT INTO `permissions` VALUES (3, '权限管理', 'PERMISSION_MANAGE', '管理权限信息', 'MENU', '/admin/permissions', 1, '2025-07-10 03:08:06', '2025-07-10 03:08:06');
INSERT INTO `permissions` VALUES (4, '查看个人信息', 'USER_PROFILE_VIEW', '查看个人信息', 'BUTTON', '/user/profile', 1, '2025-07-10 03:08:06', '2025-07-10 03:08:06');
COMMIT;
-- ----------------------------
-- Table structure for role_permissions
-- ----------------------------
DROP TABLE IF EXISTS `role_permissions`;
CREATE TABLE `role_permissions` (
`role_id` bigint NOT NULL COMMENT '角色 ID',
`permission_id` bigint NOT NULL COMMENT '权限 ID',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色 - 权限关联表';
-- ----------------------------
-- Records of role_permissions
-- ----------------------------
BEGIN;
INSERT INTO `role_permissions` VALUES (1, 1, '2025-07-10 03:08:06');
INSERT INTO `role_permissions` VALUES (1, 2, '2025-07-10 03:08:06');
INSERT INTO `role_permissions` VALUES (1, 3, '2025-07-10 03:08:06');
INSERT INTO `role_permissions` VALUES (2, 4, '2025-07-10 03:08:06');
COMMIT;
-- ----------------------------
-- Table structure for roles
-- ----------------------------
DROP TABLE IF EXISTS `roles`;
CREATE TABLE `roles` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色 ID',
`role_name` varchar(50) NOT NULL COMMENT '角色名称',
`role_code` varchar(50) NOT NULL COMMENT '角色编码',
`description` varchar(255) DEFAULT NULL COMMENT '角色描述',
`status` tinyint DEFAULT '1' COMMENT '角色状态,1 正常,0 禁用',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `role_name` (`role_name`),
UNIQUE KEY `role_code` (`role_code`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色表';
-- ----------------------------
-- Records of roles
-- ----------------------------
BEGIN;
INSERT INTO `roles` VALUES (1, '超级管理员', 'ROLE_ADMIN', '拥有系统所有权限', 1, '2025-07-10 03:08:06', '2025-07-10 03:08:06');
INSERT INTO `roles` VALUES (2, '普通用户', 'ROLE_USER', '拥有基本的用户权限', 1, '2025-07-10 03:08:06', '2025-07-10 03:08:06');
COMMIT;
-- ----------------------------
-- Table structure for user_roles
-- ----------------------------
DROP TABLE IF EXISTS `user_roles`;
CREATE TABLE `user_roles` (
`user_id` bigint NOT NULL COMMENT '用户 ID',
`role_id` bigint NOT NULL COMMENT '角色 ID',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户 - 角色关联表';
-- ----------------------------
-- Records of user_roles
-- ----------------------------
BEGIN;
INSERT INTO `user_roles` VALUES (1, 1, '2025-07-10 03:08:06');
INSERT INTO `user_roles` VALUES (2, 2, '2025-07-10 03:08:06');
COMMIT;
-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户 ID',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '用户密码',
`email` varchar(100) DEFAULT NULL COMMENT '用户邮箱',
`phone` varchar(20) DEFAULT NULL COMMENT '用户手机号',
`real_name` varchar(50) DEFAULT NULL COMMENT '用户真实姓名',
`avatar` varchar(255) DEFAULT NULL COMMENT '用户头像地址',
`status` tinyint DEFAULT '1' COMMENT '用户状态,1 正常,0 禁用',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
-- ----------------------------
-- Records of users
-- ----------------------------
BEGIN;
INSERT INTO `users` VALUES (1, 'admin', '123456', 'admin@example.com', '13800138000', '超级管理员', 'https://example.com/admin_avatar.jpg', 1, '2025-07-10 03:08:06', '2025-07-10 03:23:16');
INSERT INTO `users` VALUES (2, 'user', '123456', 'user@example.com', '13900139000', '普通用户', 'https://example.com/user_avatar.jpg', 1, '2025-07-10 03:08:06', '2025-07-10 03:23:16');
INSERT INTO `users` VALUES (3, '13800138000', '123456', '13800138000@example.com', '13900139000', '普通用户', 'https://example.com/user_avatar.jpg', 1, '2025-07-10 03:08:06', '2025-07-10 03:23:16');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
至此,不出意外,项目应该可以正常启动了
紧接着赶紧测试吧,测试说明
-- 1. 获取授权码
http://localhost:8080/oauth/authorize?response_type=code&client_id=app1&redirect_uri=http://localhost:8080/callback&scope=read_profile
-- 授权码获取token
http://localhost:8080/oauth/token
form表单参数:
grant_type=authorization_code
code=your_authorization_code
redirect_uri=http://localhost:8080/callback
Authorization:
Auth-Type: Basic Auth
UserName: client, Password: secret
-- 2. 简化模式
http://localhost:8080/oauth/authorize?response_type=token&client_id=app1&redirect_uri=http://localhost:8080/callback
-- 返回
http://localhost:8080/callback
#access_token=TmPaqFkSm2HGVxaI1iSXwRfDdoQ
&token_type=bearer
&expires_in=2758
&scope=read_profile%20write_order
-- 3. 密码模式
http://localhost:8080/oauth/token
form表单参数:
grant_type=authorization_code
code=your_authorization_code
redirect_uri=http://localhost:8080/callback
Authorization:
Auth-Type: Basic Auth
UserName: client, Password: secret
-- 4. 客户端模式
http://localhost:8080/oauth/token
form表单参数:
grant_type=client_credentials
Authorization:
Auth-Type: Basic Auth
UserName: client, Password: secret
-- 5. 刷新token
http://localhost:8080/oauth/token
form表单参数:
grant_type=refresh_token
refresh_token=your_refresh_token
-- 6. 自定义短信验证码
http://localhost:8080/oauth/token
form表单参数:
grant_type=sms_code
mobile=13800138000
code=123456
Authorization:
Auth-Type: Basic Auth
UserName: client, Password: secret