springcloud授权服务器
授权服务器搭建
一. 准备依赖
<dependencies>
<!-- nacos 注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- oauth2 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!-- web starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
1. 启动类
package com.chaoyang;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AuthorizationApplication {
public static void main(String[] args) {
SpringApplication.run(AuthorizationApplication.class, args);
}
}
2. 配置(application.yml)
server:
port: 12000
spring:
application:
name: authorization-server
cloud:
nacos:
discovery:
server-addr: www.nacos-server.com:8848
redis:
host: www.redis-server.com
port: 6379
datasource:
url: jdbc:mysql://www.db.com:3306/coin-exchange?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=false
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
3. 新建配置类(简易版)
/**
* 授权服务配置类
*/
package com.chaoyang.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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;
@Configuration // 配置类注解
@EnableAuthorizationServer // 开启授权服务
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 密码加密器
*/
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 授权(验证)管理器
*/
@Autowired
private AuthenticationManager authenticationManager;
/**
* 客户信息服务
*/
@Autowired
private UserDetailsService userDetailsService;
/**
* 添加第三方客户端
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 使用内存方式
clients.inMemory()
.withClient("coin-api")// 第三方客户端名称
.secret(passwordEncoder.encode("123")) // 第三方客户端密码
.scopes("all")// 授权范围
.accessTokenValiditySeconds(3600)// token有效期
.refreshTokenValiditySeconds(7200*7); // refresh_token有效期
super.configure(clients);
}
/**
* 配置验证管理器
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(this.authenticationManager).userDetailsService(userDetailsService);
super.configure(endpoints);
}
}
/*
* WebsecurityConfig 类
*/
package com.chaoyang.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.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import java.util.Arrays;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 禁用csrf
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().anyRequest().authenticated();
}
/**
* 使用默认的验证管理器
*/
@Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
};
/**
* 自定义客户信息
*/
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
User user = new User("amdin", "password", Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
inMemoryUserDetailsManager.createUser(user);
return inMemoryUserDetailsManager;
}
/**
* 密码管理器
*/
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
请求示例:
Redis 存储
在授权服务配置中的验证管理中添加 tokenStore
/**
* 授权服务配置类
*/
package com.chaoyang.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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;
@Configuration // 配置类注解
@EnableAuthorizationServer // 开启授权服务
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
/**
* 密码加密器
*/
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 授权(验证)管理器
*/
@Autowired
private AuthenticationManager authenticationManager;
/**
* 客户信息服务
*/
@Autowired
private UserDetailsService userDetailsService;
/**
* redis
*/
@Autowired
private RedisConnectionFactory redisConnectionFactory;
/**
* 添加第三方客户端
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 使用内存方式
clients.inMemory()
.withClient("coin-api")// 第三方客户端名称
.secret(passwordEncoder.encode("123")) // 第三方客户端密码
.scopes("all")// 授权范围
.accessTokenValiditySeconds(3600)// token有效期
.refreshTokenValiditySeconds(7200*7); // refresh_token有效期
super.configure(clients);
}
/**
* 配置验证管理器
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(this.authenticationManager).userDetailsService(userDetailsService)
.tokenStore(tokenStore());
super.configure(endpoints);
}
/**
* tokenStore
*/
private TokenStore tokenStore(){
return new RedisTokenStore(redisConnectionFactory);
}
}
/**
* 获取用户信息
*/
package com.chaoyang.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.RestController;
import java.security.Principal;
@RestController
public class UserInfoController {
@GetMapping("/user/info")
public Principal getUserPrincipal(Principal principal) {
return principal;
}
}
/**
* 开启资源服务配置
*/
package com.chaoyang.config;
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;
@EnableResourceServer
@Configuration
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
}
请求示例
JWT 版本
# 生成私钥
keytool -genkeypair -alias coinexchange -keyalg RSA -keypass coinexchange -keystore coinexchange.jks -validity 365 -storepass coinexchange
# 根据私钥生成公钥
keytool -list -rfc --keystore mindflow.jks | openssl x509 -inform pem -pubkey
-
授权服务器生成
jwt
/** * 授权服务配置类 */ package com.chaoyang.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.userdetails.UserDetailsService; 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; @Configuration // 配置类注解 @EnableAuthorizationServer // 开启授权服务 public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { /** * 密码加密器 */ @Autowired private PasswordEncoder passwordEncoder; /** * 授权(验证)管理器 */ @Autowired private AuthenticationManager authenticationManager; /** * 客户信息服务 */ @Autowired private UserDetailsService userDetailsService; /** * redis */ @Autowired private RedisConnectionFactory redisConnectionFactory; /** * 添加第三方客户端 */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { // 使用内存方式 clients.inMemory() .withClient("coin-api")// 第三方客户端名称 .secret(passwordEncoder.encode("123")) // 第三方客户端密码 .scopes("all")// 授权范围 .accessTokenValiditySeconds(3600)// token有效期 .refreshTokenValiditySeconds(7200*7); // refresh_token有效期 super.configure(clients); } /** * 验证器 */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .userDetailsService(userDetailsService) .tokenStore(tokenStore()) .tokenEnhancer(jwtAccessTokenConverter()); super.configure(endpoints); } private JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); // 加载私钥 ClassPathResource classPathResource = new ClassPathResource("mindflow.jks"); KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(classPathResource, "mindflow".toCharArray()); jwtAccessTokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("mindflow", "mindflow".toCharArray())); return jwtAccessTokenConverter; } private TokenStore tokenStore() { return new JwtTokenStore(jwtAccessTokenConverter()); } }
请求示例
4. 网关实现jwt登出引发的二次登录问题
package com.chaoyang.filter; import com.alibaba.fastjson.JSONObject; import com.ctc.wstx.util.StringUtil; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.List; import java.util.Set; @Component public class JwtCheckFilter implements GlobalFilter, Ordered { @Autowired private RedisTemplate redisTemplate; @Value("${no.require.urls:/authorization/oauth/token}") private Set<String> whitePathList; /** * 过滤器拦截用户的请求后做什么? * @param exchange * @param chain * @return */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1. 该接口是否需要token才能访问 if(isRequireToken(exchange)){ return chain.filter(exchange); } // 2. 获取Token String token = getUserToken(exchange); // 3. 判断客户的token是否有效? if(StringUtils.isEmpty(token)){ return AuthorizationError(exchange); } // 4.判断redis中是含有这个key if(!redisTemplate.hasKey(token)){ return AuthorizationError(exchange); } return chain.filter(exchange); } private String getUserToken(ServerWebExchange exchange) { String token = exchange.getRequest().getHeaders().getFirst("Authorization"); return token == null ? null : token.replace("bearer ", ""); } private Mono<Void> AuthorizationError(ServerWebExchange exchange) { ServerHttpResponse response = exchange.getResponse(); response.getHeaders().set("Content-Type", "application/json"); response.setStatusCode(HttpStatus.UNAUTHORIZED); JSONObject jsonObject = new JSONObject(); jsonObject.put("error","No Authorization"); jsonObject.put("errorMsg","Token is null or Error"); DataBuffer wrap = response.bufferFactory().wrap(jsonObject.toJSONString().getBytes()); return response.writeWith(Flux.just(wrap)); } private boolean isRequireToken(ServerWebExchange exchange) { // 获取路由数据 String path = exchange.getRequest().getURI().getPath(); System.out.println(path); System.out.println(whitePathList); // 判断是否在白名单中,在白名单中返回true 否则返回false return whitePathList.contains(path); } /** * 拦截器的顺序 * @return */ @Override public int getOrder() { return 0; } }
5. 新建权限表
-- 系统用户表
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增ID',
`username` varchar(32) NOT NULL COMMENT '用户名',
`password` varchar(255) DEFAULT NULL COMMENT '密码',
`fullname` varchar(32) DEFAULT NULL COMMENT '全名',
`mobile` varchar(20) DEFAULT NULL COMMENT '手机号',
`email` varchar(255) DEFAULT NULL COMMENT '邮箱',
`status` tinyint(4) DEFAULT '1' COMMENT '用户状态 1启用 0禁用',
`create_by` int(11) DEFAULT NULL COMMENT '创建人ID',
`modify_by` int(11) DEFAULT NULL COMMENT '修改人ID',
`created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`last_update_time` datetime DEFAULT NULL COMMENT '最后一次修改的时间',
`is_deleted` tinyint(4) DEFAULT '0' COMMENT '逻辑删除 0未删除 1已删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
-- 系统角色表
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增',
`name` varchar(32) DEFAULT NULL COMMENT '角色名称',
`code` varchar(32) DEFAULT NULL COMMENT '角色编码',
`description` varchar(255) DEFAULT NULL COMMENT '角色描述',
`modify_by` int(11) DEFAULT NULL COMMENT '修改人名称',
`status` tinyint(4) DEFAULT '1' COMMENT '角色状态 1有效 0失效',
`created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`last_update_time` datetime DEFAULT NULL COMMENT '最后一次更新时间',
`is_deleted` tinyint(4) DEFAULT '0' COMMENT '逻辑删除 0未删除 1已删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
-- 系统权限表
CREATE TABLE `sys_privilege` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`menu_id` int(11) DEFAULT NULL,
`name` varchar(32) DEFAULT NULL COMMENT '功能点名称',
`description` varchar(255) DEFAULT NULL COMMENT '功能描述',
`url` varchar(1024) DEFAULT NULL,
`method` varchar(32) DEFAULT NULL COMMENT '请求方式',
`create_by` int(11) DEFAULT NULL COMMENT '创建人ID',
`modify_by` int(11) DEFAULT NULL COMMENT '修改人ID',
`created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`last_update_time` datetime DEFAULT NULL COMMENT '修改时间',
`is_deleted` tinyint(4) DEFAULT '0' COMMENT '逻辑删除 0未删除 1已删除',
`status` tinyint(4) DEFAULT '1' COMMENT '状态: 0失效 1有效',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
-- 角色与权限的多对多的表
CREATE TABLE `sys_role_privilege` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` int(11) DEFAULT NULL COMMENT '角色ID',
`privilege_id` int(11) DEFAULT NULL COMMENT '权限ID',
`is_deleted` tinyint(4) DEFAULT '0' COMMENT '逻辑删除 0未删除 1已删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
-- 用户和角色的多对多的表
CREATE TABLE `sys_user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` int(11) DEFAULT NULL COMMENT '角色ID',
`user_id` int(11) DEFAULT NULL COMMENT '用户ID',
`create_by` int(11) DEFAULT NULL COMMENT '创建人ID',
`modify_by` int(11) DEFAULT NULL COMMENT '修改人ID',
`created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`last_update_time` datetime DEFAULT NULL COMMENT '最后一次更新时间',
`status` tinyint(4) DEFAULT '1' COMMENT '状态: 1有效 0无效',
`is_deleted` tinyint(4) DEFAULT '0' COMMENT '逻辑删除 0未删除 1已删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
-- 系统菜单表
CREATE TABLE `sys_menu` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parent_id` int(11) DEFAULT NULL COMMENT '上级菜单ID',
`parent_key` varchar(32) DEFAULT NULL COMMENT '上级菜单唯一KEY值',
`type` tinyint(4) DEFAULT '1' COMMENT '类型 1-分类 2-节点',
`name` varchar(32) DEFAULT NULL COMMENT '名称',
`description` varchar(255) DEFAULT NULL COMMENT '描述信息',
`target_url` varchar(255) DEFAULT NULL COMMENT '目标地址',
`sort` int(11) DEFAULT '0' COMMENT '排序索引',
`status` tinyint(4) DEFAULT '1' COMMENT '状态 0-无效; 1-有效;',
`create_by` int(11) DEFAULT NULL COMMENT '创建人ID',
`modify_by` int(11) DEFAULT NULL COMMENT '修改人ID',
`created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`last_update_time` datetime DEFAULT NULL COMMENT '最后更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 角色与菜单的多对多的表
CREATE TABLE `sys_role_menu` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` int(11) DEFAULT NULL COMMENT '角色ID',
`menu_id` int(11) DEFAULT NULL COMMENT '菜单ID',
`create_by` int(11) DEFAULT NULL COMMENT '创建人ID',
`modify_by` int(11) DEFAULT NULL COMMENT '修改人ID',
`created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`last_update_time` datetime DEFAULT NULL COMMENT '最后一次更新时间',
`is_deleted` tinyint(4) DEFAULT '0' COMMENT '逻辑删除 0未删除 1已删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 会员表
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) DEFAULT NULL COMMENT '用户名',
`password` varchar(255) DEFAULT NULL COMMENT '密码',
`status` tinyint(4) DEFAULT '1' COMMENT '状态',
`create_by` int(11) DEFAULT NULL COMMENT '创建人ID',
`mobile` varchar(20) DEFAULT NULL COMMENT '手机号码',
`email` varchar(255) DEFAULT NULL COMMENT '邮箱',
`created` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`last_update_time` datetime DEFAULT NULL COMMENT '最后更新时间',
`is_deleted` tinyint(4) DEFAULT '0' COMMENT '逻辑删除 0未删除 1已删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端