Oauth2有以下授权模式:
- 授权码模式(Authorization Code)
- 隐式授权模式(Implicit)
- 密码模式(Resource Owner Password Credentials)
- 客户端模式(Client Credentials)
其中授权码模式和密码模式应用较多
授权码模式
授权码授权流程:
- 客户端请求第三方授权
- 认证服务器生成用户授权协议,用户同意授权,认证服务器将协议唯一号(授权码)响应给客户端
- 客户端获得授权码, 然后请求认证服务器申请令牌
- 认证服务器生成令牌后向客户端响应令牌
- 客户端请求资源服务器的资源,资源服务器校验令牌合法性,完成授权
- 资源服务器返回受保护资源
请求认证服务获取授权码
固定表名:oauth_client_details(此表名不可修改)
Get请求:
http://localhost:9200/oauth/authorize?
client_id=changgou&response_type=code&scop=app&redirect_uri=http://localhost
client_id:客户端id,和授权配置类中设置的客户端id一致。
response_type:授权码模式固定为code
scop:客户端范围,和授权配置类中设置的scop一致。
redirect_uri:跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)
获取令牌【授权码模式】
Post请求:
http://localhost:9200/oauth/token
【header】
clinet_id:客户端名
client_secret:客户端密码
【body】
grant_type:授权类型,填写authorization_code,表示授权码模式
code:授权码,就是刚刚获取的授权码,注意:授权码只使用一次就无效了,需要重新申请。
redirect_uri:申请授权码时的跳转url,一定和申请授权码时用的redirect_uri一致。
【Authorization】
TYPE:Basic Auth
username:账号
password:密码
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJhcHAiXSwibmFtZSI6bnVsbCwiaWQiOm51bGwsImV4cCI6MTYwMDYzMDU2MiwianRpIjoiMTBlNThkYWMtMzRmZC00OWQxLThlZjctZGUzYzZlZjU3Mzg5IiwiY2xpZW50X2lkIjoiY2hhbmdnb3UiLCJ1c2VybmFtZSI6ImNoYW5nZ291In0.JrDYPSqooM57epYYdiipMf6tC9i6MZolySHf4an4BbTcZIvqLy39UIsXPK27bL8LGtaaZYFKW0FKHuNlUrX5hg8qCZhOuzOJGAJfGAFf1DiVCOVph-pE4P0va3MXkUYmLSXxgcJBSTuG31L3nnatvPU0__WfbjnJjAD_DWb20wOtET4E8Iv2L1kcmtSSswYnlY6actlqfdns93JHmZewPh8i647H0rvBzIlO0N6ZMkmfWxRL9dMv80dwSMCmsvHD0W0I8w4c8Ns18iwzYFPjqtEajlcW6DmERI1sd94aUl96cBD0uijpgpec226KclZ8h-_oRzGb3pSAqiaJQX711w",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJhcHAiXSwiYXRpIjoiMTBlNThkYWMtMzRmZC00OWQxLThlZjctZGUzYzZlZjU3Mzg5IiwibmFtZSI6bnVsbCwiaWQiOm51bGwsImV4cCI6MTYwMDYzMDU2MiwianRpIjoiMDAzN2Q4MzUtYzQ0OC00NjI5LWI2MTYtMmNmNDVkNDQxYzc3IiwiY2xpZW50X2lkIjoiY2hhbmdnb3UiLCJ1c2VybmFtZSI6ImNoYW5nZ291In0.hbksRM6PkOPEcBs7VS5W8-EUNSraKo5TWgjJk53tZC6rOxyTGd9HOf7dM6CDZ9GuEKoSWVSARMcyYU6RyGJrkURc4VBBXjGydrO3Zn7esoR291CVS8gLLjoMhYwq_rchgrsx9P6MOIT0PapsQr321I7d6EwYR7zKU8cuMA0L1HqewpZxjP1BtVtMln52CUADqzYa8uuPb4FkORbfzVR-mjBZrkM96lXxa5z2asMEBsve9CGxhXGvaWW5Iw_DNNpjbYvT5v2qJ5z7BzaUydmzhoX_GIcyo2UedycoevBgSItIEE-C7vSUvHswuL_aUsRBxCsdBe2AqJZE-r4mQ5EZoQ",
"expires_in": 43199,
"scope": "app",
"jti": "10e58dac-34fd-49d1-8ef7-de3c6ef57389"
校验令牌
Get请求:
http://localhost:9200/oauth/check_token?token=[access_token]
结果:
{
"scope": [
"app"
],
"name": null,
"active": true,
"id": null,
"exp": 1600630562,
"jti": "10e58dac-34fd-49d1-8ef7-de3c6ef57389",
"client_id": "changgou",
"username": "changgou"
}
刷新令牌(令牌和时间都会更新)
Post:http://localhost:9200/oauth/token
参数:
grant_type: 固定为 refresh_token
refresh_token:刷新令牌(注意不是access_token,而是refresh_token)
结果:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJhcHAiXSwiZXhwIjoxNjAwNjMyMTkxLCJqdGkiOiJkMGMwMzRkZS00YjY5LTQ1YTYtODIyZi00NTkxMDg0OWVjMWUiLCJjbGllbnRfaWQiOiJjaGFuZ2dvdSJ9.iLcwZUHjeFHKWwt_54CpxEEiPpzPqjHJSNeq5RYctGNygmFu-HxhOgRy83h2st-GucfIPGyEPiDt2JtndwhWVpdLY0JrrF2n2zeFLEtuc1Y7IzoJrsBKyHSN6sDErkdwnxuKAVGXIFp8miQy1qXsR7SN-LQ5i1j8S56vfyFvxitIiTqCkj4lcDUU0aki9EpsKDBt9K-RQ9sddqcpV9L3Y2wT0PeBeXRbVj25fCc7WOrSod0qQHDcIz3PHs4RiHfRENg2lm-ZwpuTHIBQolKqn3_oLUcUTAS9LkzodtZ_0eqPOvp1izfT3e46laRZxFQ-AcNNvSGm8rvwbQoQaICTTw",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJhcHAiXSwiYXRpIjoiZDBjMDM0ZGUtNGI2OS00NWE2LTgyMmYtNDU5MTA4NDllYzFlIiwiZXhwIjoxNjAwNjMwNTYyLCJqdGkiOiIwMDM3ZDgzNS1jNDQ4LTQ2MjktYjYxNi0yY2Y0NWQ0NDFjNzciLCJjbGllbnRfaWQiOiJjaGFuZ2dvdSJ9.WBLLpmQ4ScekpQ7SjMrLqEtvUr2NXO1H-WVs13GK1LqBcUUrP-Qbz144itjraT6Bckw2gebojbbljQ-nze3ubS--d9zQkQL6y3cqUn7KNfTPmxIK-BXQ3QD5Rb7SMP4fX4IgeGE77uELHOXN9zCu4_Er7wX7-IhkllwBicOWP7FiXi5UIVDpf380NnWBwXlxugGI5DhW40ON50wdTnkvjmMZozRYgOO8LqejB6MKj4zX9hMoRs3eM6y2eXKj3spWiRr4e0oRZwx9m9X-0DB499hCWd6CQtxgIq4c5b6dZjNAw9XXrego9xQqcdRbKSLhbvK5fVXbt9KhEV6tqVic9A",
"expires_in": 43199,
"scope": "app",
"jti": "d0c034de-4b69-45a6-822f-45910849ec1e"
}
获取令牌【密码模式】
密码模式与授权码模式的区别是申请令牌不再使用授权码,而是直接通过用户名和密码即可申请令牌,一般用于内部系统之间,进行授权访问。
申请令牌:
Post请求:
http://localhost:9200/oauth/token
【header】
clinet_id:客户端名
client_secret:客户端密码
【body】
携带参数:
grant_type:密码模式授权填写password
username:账号
password:密码
结果:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJhcHAiXSwibmFtZSI6bnVsbCwiaWQiOm51bGwsImV4cCI6MTYwMDYzMjk1MCwiYXV0aG9yaXRpZXMiOlsic2Vja2lsbF9saXN0IiwiZ29vZHNfbGlzdCJdLCJqdGkiOiIzZTEzOGMzNS0xZjUzLTQ0YzMtYTMyMC04MTc1MWI2ZDllYTYiLCJjbGllbnRfaWQiOiJjaGFuZ2dvdSIsInVzZXJuYW1lIjoiaXRoZWltYSJ9.utWFXzdHT2DhiHEozbqv61MubL96rWg_V4evU1dG4Pq-33nYXZ1wTi5XBvYiLDzwCroVr69B5nwx21VoiG3cz2QCyVyeyS2k3iRX8mXMWJcaa_1ojjKyLLHpqWJ0Xerr1OIOaN-RLJm2Wlu4InSyTUgxCOdDXYD43g2o37nL51b3wBkQrRtLs_qMwIuj6a4oM2gW53iY0mN84Hw4AAD2UBFPJ6Mhix-Ip1DsSqavDPG3Do2WJrfu78q_O6b_UP9SaO7fuURj6szoSbqmqKudze-AWy15Gpeemz2WIt0_0jB4nO5U7Xr6aGjRseN9wivhaIfbz4vHquUJog7z0zdE0g",
"token_type": "bearer",
"refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJhcHAiXSwiYXRpIjoiM2UxMzhjMzUtMWY1My00NGMzLWEzMjAtODE3NTFiNmQ5ZWE2IiwibmFtZSI6bnVsbCwiaWQiOm51bGwsImV4cCI6MTYwMDYzMjk1MCwiYXV0aG9yaXRpZXMiOlsic2Vja2lsbF9saXN0IiwiZ29vZHNfbGlzdCJdLCJqdGkiOiJkNmFjOGNiYS1hNmM1LTRhOTQtOWMzYy0yMzFmMmY4MjgyNTEiLCJjbGllbnRfaWQiOiJjaGFuZ2dvdSIsInVzZXJuYW1lIjoiaXRoZWltYSJ9.R5jNy3-z1vg1a2gJBionUwUVDMC6Ig6hagUePIiIvovBayO72UTJX46LOBi0T6ORbF3Z5n3CFRH4cBw6LtOv1h7y2VZyYMKFtJEPzrfZe-KmqIMo0n9B1Mc9qoEH-AOOKzFSOcrtzU6qTgsgc_YrrthC-5srsg8UB4XDkahRDIytCu48zn0PNXL8I961kMNaI-s426Fo2nm_-yWDqktbQi1nrtFg37QrvO4oBBHYHj0Zf0sSshqGV25uKjAelGpzO95-7paW9cOBK2ue3GBP-RKFfUnSvCX_HZj6pZX8DnoIAEb1EbT0tuWT8zYmwKWpXzNAW12rQj7pMPIYjq6NGw",
"expires_in": 43199,
"scope": "app",
"jti": "3e138c35-1f53-44c3-a320-81751b6d9ea6"
}
资源服务授权(服务对接)
资源服务拥有要访问的受保护资源,客户携带令牌访问资源服务,如果令牌合法则可以成功访问资源服务中的服务。
流程图:
- 客户端请求认证服务申请令牌
- 认证服务生成令牌认证服务采用非对称加密算法,使用私钥生成令牌。
- 客户端携带令牌访问资源服务客户端在Http header中添加:Authorization: Bearer令牌。
- 资源服务请求认证服务校验令牌的有效性资源服务接收到令牌,使用公钥校验令牌的合法性。
- 令牌有效,资源服务向客户端响应资源信息
步骤:
- 导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
- 配置每个系统的Http请求路径安全控制策略以及读取公钥信息识别令牌,如下:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)//激活方法上的PreAuthorize注解
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
//公钥
private static final String PUBLIC_KEY = "public.key";
/***
* 定义JwtTokenStore
* @param jwtAccessTokenConverter
* @return
*/
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
/***
* 定义JJwtAccessTokenConverter
* @return
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(getPubKey());
return converter;
}
/**
* 获取非对称加密公钥 Key
* @return 公钥 Key
*/
private String getPubKey() {
Resource resource = new ClassPathResource(PUBLIC_KEY);
try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
BufferedReader br = new BufferedReader(inputStreamReader);
return br.lines().collect(Collectors.joining("\n"));
} catch (IOException ioe) {
return null;
}
}
/***
* Http安全配置,对每个到达系统的http请求链接进行校验
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//所有请求必须认证通过
http.authorizeRequests()
//下边的路径放行
.antMatchers(
"/user/add","/user/load/**"). //配置地址放行
permitAll()
.anyRequest().
authenticated(); //其他地址需要认证授权
}
}
- 测试
不带令牌测试:localhost:9005/user
返回错误信息:
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this
resource"
}
带令牌测试,返回正确信息:
{
"flag": true,
"code": 20000,
"message": "查询成功",
"data": [
{
...
}