通过微信实现第三方认证:
1.OAuth2协议工作原理
资源:用户信息,在微信中存储。
资源拥有者:用户是用户信息资源的拥有者。
认证服务:微信负责认证当前用户的身份,负责为客户端颁发令牌。
客户端:客户端会携带令牌请求微信获取用户信息,黑马程序员网站即客户端,黑马网站需要在浏览器打开。
Oauth2包括以下角色:
1、客户端(Client)
本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源,比如:手机客户端、浏览器等。
上边示例中黑马网站即为客户端,它需要通过浏览器打开。
2、资源拥有者(Resource Owner)
通常为用户,也可以是应用程序,即该资源的拥有者。
A表示 客户端请求资源拥有者授权。
B表示 资源拥有者授权客户端即黑马网站访问自己的用户信息。
3、授权服务器(也称认证服务器)(Authorization Server)
认证服务器对资源拥有者进行认证,还会对客户端进行认证并颁发令牌。
C 客户端即黑马网站携带授权码请求认证。
D认证通过颁发令牌。
4、资源服务器(Resource Server)
存储资源的服务器。
E表示客户端即黑马网站携带令牌请求资源服务器获取资源。
F表示资源服务器校验令牌通过后提供受保护资源。
2.OAuth2协议在学成在线项目中的应用
Oauth2是一个标准的开放的授权协议,应用程序可以根据自己的要求去使用Oauth2,本项目使用Oauth2实现如下目标:
1、学成在线访问第三方系统的资源。
本项目要接入微信扫码登录所以本项目要使用OAuth2协议访问微信中的用户信息。
2、外部系统访问学成在线的资源 。
同样当第三方系统想要访问学成在线网站的资源也可以基于OAuth2协议。
3、学成在线前端(客户端) 访问学成在线微服务的资源。
本项目是前后端分离架构,前端访问微服务资源也可以基于OAuth2协议进行统一服务认证。
1. OAuth2协议最常用的两种授权模式
(1)授权码模式
还以黑马网站微信扫码登录为例进行说明:
1、用户打开浏览器。
2、通过浏览器访问客户端即黑马网站。
3、用户通过浏览器向认证服务请求授权,请求授权时会携带客户端的URL,此URL为下发授权码的重定向地址。
4、认证服务向资源拥有者返回授权页面。
5、资源拥有者亲自授权同意。
6、通过浏览器向认证服务发送授权同意。
7、认证服务向客户端地址重定向并携带授权码。
8、客户端即黑马网站收到授权码。
9、客户端携带授权码向认证服务申请令牌。
10、认证服务向客户端颁发令牌。
下面就来测试一下授权码模式:
1.在auth工程的config包下定义两个配置类AuthorizationServer和TokenConfig:
/**
* @description 授权服务器配置
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
@Resource(name="authorizationServerTokenServicesCustom")
private AuthorizationServerTokenServices authorizationServerTokenServices;
@Autowired
private AuthenticationManager authenticationManager;
//客户端详情服务
@Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.inMemory()// 使用in-memory存储
.withClient("XcWebApp")// client_id
/*
//使用明文的
.secret("XcWebApp")//客户端密钥
*/
//使用加密的
.secret(new BCryptPasswordEncoder().encode("XcWebApp"))//客户端密钥
.resourceIds("xuecheng-plus")//资源列表
.authorizedGrantTypes("authorization_code", "password","client_credentials","implicit","refresh_token")// 该client允许的授权类型authorization_code,password,refresh_token,implicit,client_credentials
.scopes("all")// 允许的授权范围
.autoApprove(false)//false跳转到授权页面
//客户端接收授权码的重定向地址
.redirectUris("http://www.51xuecheng.cn")
;
}
//令牌端点的访问配置
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager)//认证管理器
.tokenServices(authorizationServerTokenServices)//令牌管理服务
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
//令牌端点的安全配置
@Override
public void configure(AuthorizationServerSecurityConfigurer security){
security
.tokenKeyAccess("permitAll()") //oauth/token_key是公开
.checkTokenAccess("permitAll()") //oauth/check_token公开
.allowFormAuthenticationForClients() //表单认证(申请令牌)
;
}
}
这段代码是一个授权服务器的配置类,用于配置OAuth2授权服务器的行为。让我们逐步解释每个部分的作用:
@Configuration声明这是一个配置类,用于定义Bean。
@EnableAuthorizationServer启用OAuth2授权服务器功能。
AuthorizationServerTokenServices authorizationServerTokenServices;
注入自定义的授权服务器令牌服务,用于管理令牌的生成和验证。
AuthenticationManager authenticationManager;
注入身份验证管理器,用于验证用户的身份。
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory() // 使用in-memory存储,在内存存储令牌
.withClient("XcWebApp") // 客户端ID
.secret(new BCryptPasswordEncoder().encode("XcWebApp")) // 客户端密钥(使用BCryptPasswordEncoder进行加密)
.resourceIds("xuecheng-plus") // 资源ID列表
.authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token") // 授权类型列表
.scopes("all") // 授权范围
.autoApprove(false) // 是否自动批准(false跳转到授权页面)
.redirectUris("http://www.51xuecheng.cn"); // 授权码重定向地址
}
配置客户端详情服务,指定了客户端ID、密钥、资源ID、授权类型、授权范围等信息。
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(authenticationManager) // 指定认证管理器
.tokenServices(authorizationServerTokenServices) // 指定令牌服务
.allowedTokenEndpointRequestMethods(HttpMethod.POST); // 允许的令牌端点请求方法
}
配置令牌端点的访问,包括认证管理器、令牌服务等。
这个配置类定义了OAuth2授权服务器的行为,包括配置客户端详情服务、令牌端点访问以及令牌端点的安全配置。通过这些配置,可以定义授权服务器的授权策略、客户端管理、令牌管理等行为。
@Configuration
public class TokenConfig {
private String SIGNING_KEY = "mq123";
@Autowired
TokenStore tokenStore;
// @Bean
// public TokenStore tokenStore() {
// //使用内存存储令牌(普通令牌)
// return new InMemoryTokenStore();
// }
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
//令牌管理服务
@Bean(name="authorizationServerTokenServicesCustom")
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service=new DefaultTokenServices();
service.setSupportRefreshToken(true);//支持刷新令牌
service.setTokenStore(tokenStore);//令牌存储策略
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
service.setTokenEnhancer(tokenEnhancerChain);
service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
return service;
}
}
这是一个令牌相关配置类 `TokenConfig`,用于配置令牌相关的信息,包括令牌存储、JWT令牌转换器、以及令牌管理服务。
注意:Spring Security框架默认是支持普通令牌的,在一开始测试的时候我们也是使用普通令牌。等到后面改进为jwt令牌的时候,再把jwt令牌需要的代码放开,把普通令牌的存储方法注释掉。
让我们逐步解释每个部分的作用:
private String SIGNING_KEY = "mq123";
定义了签名密钥,用于JWT令牌的签名和验证。
@Autowired
TokenStore tokenStore;
// @Bean
// public TokenStore tokenStore() {
// //使用内存存储令牌(普通令牌)
// return new InMemoryTokenStore();
// }
自动注入了令牌存储,这里是一个普通令牌的 `tokenStore`存储方法,在这里测试的时候使用。后面更换为jwt令牌的时候,会将其注释掉。
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
这是使用 JWT 令牌存储的方法,将 JWT 令牌转换器作为参数传递给 `JwtTokenStore`。
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(SIGNING_KEY);
return converter;
}
配置了 JWT 令牌转换器,并设置了签名密钥。
@Bean(name="authorizationServerTokenServicesCustom")
public AuthorizationServerTokenServices tokenService() {
DefaultTokenServices service=new DefaultTokenServices();
service.setSupportRefreshToken(true); // 支持刷新令牌
service.setTokenStore(tokenStore); // 设置令牌存储策略
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
service.setTokenEnhancer(tokenEnhancerChain);
service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
return service;
}
配置了令牌管理服务 `tokenService`,使用了默认的令牌服务实现 `DefaultTokenServices`。
设置了支持刷新令牌、令牌存储策略、令牌增强器链、令牌有效期等参数。
通过这个配置类,你可以灵活地配置令牌的存储方式、JWT令牌转换器、以及令牌管理服务,从而实现 OAuth2 授权服务器的令牌管理功能。
2. 重启认证服务,get请求获取授权码
地址: http://localhost:63070/auth/oauth/authorize?client_id=XcWebApp&response_type=code&scope=all&redirect_uri=http://www.51xuecheng.cn
参数列表如下:
- client_id:客户端准入标识。
- response_type:授权码模式固定为code。
- scope:客户端权限。
- redirect_uri:跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)。
输入账号zhangsan、密码123登录成功,输入/oauth/authorize?client_id=XcWebApp&response_type=code&scope=all&redirect_uri=http://www.51xuecheng.cn
显示授权页面
授权“XcWebApp”访问自己的受保护资源?
选择同意。
请求成功,重定向至http://www.51xuecheng.cn/?code=授权码,比如:http://www.51xuecheng.cn/?code=Wqjb5H
3.使用httpclient工具post申请令牌
参数列表如下
- client_id:客户端准入标识。
- client_secret:客户端秘钥。
- grant_type:授权类型,填写authorization_code,表示授权码模式
- code:授权码,就是刚刚获取的授权码,注意:授权码只使用一次就无效了,需要重新申请。
- redirect_uri:申请授权码时的跳转url,一定和申请授权码时用的redirect_uri一致。
httpclient脚本如下:
### 授权码模式
### 第一步申请授权码(浏览器请求)/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.51xuecheng.cn
### 第二步申请令牌
POST {{auth_host}}/auth/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=authorization_code&code=CTvCrB&redirect_uri=http://www.51xuecheng.cn
(2)密码模式
1、资源拥有者提供账号和密码
2、客户端向认证服务申请令牌,请求中携带账号和密码
3、认证服务校验账号和密码正确颁发令牌。
测试:
1、POST请求获取令牌
/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username=shangsan&password=123
参数列表如下:
- client_id:客户端准入标识。
- client_secret:客户端秘钥。
- grant_type:授权类型,填写password表示密码模式
- username:资源拥有者用户名。
- password:资源拥有者密码。
2、授权服务器将令牌(access_token)发送给client
使用httpclient进行测试:
### 密码模式
POST {{auth_host}}/auth/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username=zhangsan&password=123
3.jwt令牌
完成了上述操作后,再来看我们需要做的步骤:
这里存在一个问题:
就是校验令牌需要远程请求认证服务,客户端的每次访问都会远程校验,执行性能低。
如果能够让资源服务自己校验令牌的合法性将省去远程请求认证服务的成本,提高了性能。
令牌采用JWT格式即可解决上边的问题,用户认证通过后会得到一个JWT令牌,JWT令牌中已经包括了用户相关的信息,客户端只需要携带JWT访问资源服务,资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证服务完成授权。
jwt令牌原理见这篇博客:
1.进行jwt令牌的测试
重启认证服务。
使用httpclient通过密码模式申请令牌:
### 密码模式
POST {{auth_host}}/oauth/token?client_id=XcWebApp&client_secret=XcWebApp&grant_type=password&username=zhangsan&password=123
生成jwt的示例如下:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2NjQzMzE2OTUsImF1dGhvcml0aWVzIjpbInAxIl0sImp0aSI6ImU5ZDNkMGZkLTI0Y2ItNDRjOC04YzEwLTI1NmIzNGY4ZGZjYyIsImNsaWVudF9pZCI6ImMxIn0.-9SKI-qUqKhKcs8Gb80Rascx-JxqsNZxxXoPo82d8SM",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiJlOWQzZDBmZC0yNGNiLTQ0YzgtOGMxMC0yNTZiMzRmOGRmY2MiLCJleHAiOjE2NjQ1ODM2OTUsImF1dGhvcml0aWVzIjpbInAxIl0sImp0aSI6ImRjNTRjNTRkLTA0YTMtNDIzNS04MmY3LTFkOWZkMmFjM2VmNSIsImNsaWVudF9pZCI6ImMxIn0.Wsw1Jc-Kd_GFqEugzdfoSsMY6inC8OQsraA21WjWtT8",
"expires_in": 7199,
"scope": "all",
"jti": "e9d3d0fd-24cb-44c8-8c10-256b34f8dfcc"
}
1、access_token,生成的jwt令牌,用于访问资源使用。
2、token_type,bearer是在RFC6750中定义的一种token类型,在携带jwt访问资源时需要在head中加入bearer jwt令牌内容
3、refresh_token,当jwt令牌快过期时使用刷新令牌可以再次生成jwt令牌。
4、expires_in:过期时间(秒)
5、scope,令牌的权限范围,服务端可以根据令牌的权限范围去对令牌授权。
6、jti:令牌的唯一标识。
我们可以通过check_token接口校验jwt令牌:
###校验jwt令牌
POST {{auth_host}}/oauth/check_token?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJzdHUxIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTY2NDM3MTc4MCwiYXV0aG9yaXRpZXMiOlsicDEiXSwianRpIjoiZjBhM2NkZWItMzk5ZC00OGYwLTg4MDQtZWNhNjM4YWQ4ODU3IiwiY2xpZW50X2lkIjoiYzEifQ.qy46CSCJsH3eXWTHgdcntZhzcSzfRQlBU0dxAjZcsUw
2.携带令牌访问资源服务
拿到了jwt令牌下一步就要携带令牌去访问资源服务中的资源,本项目各个微服务就是资源服务,比如:内容管理服务,客户端申请到jwt令牌,携带jwt去内容管理服务查询课程信息,此时内容管理服务要对jwt进行校验,只有jwt合法才可以继续访问。如下图:
在这里我们先在content工程中进行测试:
content工程所需要的依赖,我们一开始就已经全部配好了,详见下面这篇博客:
黑马程序员-学成在线项目-内容管理笔记(1)-所需依赖配置-CSDN博客
这里我们为了在content工程中进行测试,所以content-api工程下的config包中,我们也需要同意将之前在auth工程下的config包中定义的两个配置类AuthorizationServer和TokenConfig拷贝过去。
另外注意在ResouceServerConfig类中需要修改,让在content工程中定义的某些接口的路径需要认证:
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
//当你使用网关进行jwt校验的时候,下面这行就不需要了
// .antMatchers("/r/**","/course/**").authenticated()//所有/r/**的请求必须认证通过
.anyRequest().permitAll()
;
}
等后面用到网关认证的时候,需要注释掉,现在测试先放开。
重启内容管理服务
使用httpclient测试。