分布式认证中心第一集 引入oauth2授权服务器
前情提要:一个“不雅观”的痛点
在上一季《Security第十六集:引入JWT》中,不知道大家有没有发现一个极其尴尬的画面——
两个微服务里,各自建了一个JwtUtils类,各自定义了一模一样的SECRET_KEY,还必须确保这俩密钥值完全相同。
这画面像什么?像极了异地恋情侣非得约定同一句暗号才能确认“是我本人”。代码冗余不说,只要密钥一换,所有服务都得跟着改,改到天荒地老。
有同学可能会问:“那怎么办?总不能让他们一直这么‘异地’下去吧?”
别急,OAuth2 就是来当这个月老的。今天我们就手把手把Security和OAuth2撮合成一对“认证授权CP”,让它们分工明确、各司其职,彻底终结那段“密钥复制粘贴”的苦日子。
第一幕:认清各自的本职工作
在动手改造之前,咱先把职责掰扯清楚,免得后面迷糊:
Spring Security 认证(Authentication) 验明正身——“你说你是张三,密码拿来我瞅瞅”
OAuth2 授权(Authorization) 发放通行证——“身份没问题,给你发个令牌,拿着它去访问别的服务吧”
简单说就是:Security负责“验人”,OAuth2负责“发牌”。
而我们今天要做的,就是把原来那个单纯的Security认证服务,改造成一个OAuth2授权服务器——让它既能验人,又能发牌,两全其美。
第二幕:动手改造,三步搞定
1.引入pom依赖
<!-- OAuth2 依赖 -->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.3.9.RELEASE</version>
</dependency>
2.创建 OAuth2AuthorizationServerConfig,配置客户端信息以及授权类型
@Configuration
//启用授权服务器功能
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
//用于验证用户的用户名和密码(密码模式需要)
@Autowired
private AuthenticationManager authenticationManager;
//用于加密和验证客户端密钥
@Autowired
private PasswordEncoder passwordEncoder;
//定义令牌的存储方式
@Bean
public TokenStore tokenStore() {
// 使用 JWT 方式存储令牌
// JWT 是一种自包含的令牌格式,不需要在服务器端存储
return new JwtTokenStore(accessTokenConverter());
}
//JWT 令牌转换器
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
// 注意:这里使用的是对称密钥(默认)
// 生产环境建议使用非对称密钥(RSA)
return converter;
}
//这里定义了哪些应用可以访问 OAuth2 服务
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// inMemory() - 将客户端信息存储在内存中
// 生产环境应该使用 .jdbc() 存储在数据库中
clients.inMemory()
// 配置第一个客户端应用 , 客户端 ID(类似用户名)
.withClient("client-app")
// 客户端密钥(类似密码),需要加密
.secret(passwordEncoder.encode("secret123"))
// 允许的授权类型(四种模式)授权码模式,密码模式,客户端凭证模式,隐式模式,刷新令牌
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
// 允许的权限范围
.scopes("read", "write")
// 回调地址
.redirectUris("http://localhost:8080/callback")
// 自动批准,不需要用户手动确认授权
.autoApprove(true)
// 访问令牌有效期:3600秒(1小时)
.accessTokenValiditySeconds(3600)
// 刷新令牌有效期:7200秒(2小时)
.refreshTokenValiditySeconds(7200)
.and()
// 配置下一个客户端
.withClient("web-app")
.secret(passwordEncoder.encode("web-secret"))
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
.scopes("read", "write")
.redirectUris("http://localhost:8080/web/callback")
.autoApprove(true)
.accessTokenValiditySeconds(3600)
.refreshTokenValiditySeconds(7200);
}
//配置授权服务器端点
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
// 设置认证管理器,用于密码模式的用户认证
.authenticationManager(authenticationManager)
// 设置令牌存储方式(JWT)
.tokenStore(tokenStore())
// 设置令牌转换器(JWT 转换)
.accessTokenConverter(accessTokenConverter());
}
//配置授权服务器的安全策略
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
// /oauth/token_key 端点允许所有人访问
// 这个端点用于获取 JWT 签名的公钥(如果使用非对称加密)
.tokenKeyAccess("permitAll()")
// /oauth/check_token 端点需要认证后才能访问
// 这个端点用于验证令牌的有效性
.checkTokenAccess("isAuthenticated()")
// 允许客户端通过表单方式提交认证信息
// 这样客户端可以在 POST body 中传递 client_id 和 client_secret
.allowFormAuthenticationForClients();
}
}
3.改造一下 MyWebSecurityConfigAdapter,放行 oauth2自带的接口,并创建一个AuthenticationManager对象
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().httpBasic();
http.authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.anyRequest().authenticated();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
第三幕:实战测试,看看效果
先来测试一下密码模式
本服务端口 8129,用post请求 http://localhost:8129/oauth/token
Authorization 中 选择 Basic Auth 并 填入 用户名 client-app, 密码 secret123,注意 这个用户名密码 是
.withClient("client-app")
// 客户端密钥(类似密码),需要加密
.secret(passwordEncoder.encode("secret123"))
不是users表中 的 用户名密码
然后在 body中添加参数,注意选择 x-www-form-urlencoded
grant_type是
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
哪个模式访问就写哪个
这里的 username 和 password 才是 数据库中 users表的 用户名密码
scope的值是
// 允许的权限范围
.scopes("read", "write")
现在我们来 send 一下
返回的这一大串json,就是 oauth2的令牌。
客户端凭证模式
这个跟密码模式的区别
1,无需提供 users 的用户名密码
2,grant_type 的值 变成 client_credentials
这个模式通常用于服务与服务之间的内部调用,比如定时任务、微服务互相拉取数据等场景——没有真人用户,只有机器在互相对话。
下集预告
这一集我们把Security认证服务成功改造为OAuth2授权服务器,跑通了密码模式和客户端凭证模式。
下一集,我们将真正创建一个前端应用,完整跑通密码模式和授权码模式的登录流程。至于隐式模式没啥卵用,可以忘掉它
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)