# oauth2 管理
使用 oauth2 功能首先需要按照 安全管理 的步骤开启 spring security 的相关功能。
# 快速启动
1 在项目中加入 oauth 相关的依赖
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.yishuifengxiao.common</groupId>
<artifactId>common-spring-boot-starter</artifactId>
<version>4.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2 在项目加入以下代码
@Configuration
public class CustomOauth2Config extends Oauth2Config{
}
3 加上@EnableResourceServer
和 @EnableAuthorizationServer
注解
完全开启示例代码如下:
@Configuration
@EnableWebSecurity
@EnableResourceServer
@EnableAuthorizationServer
public class SecurityConfig extends AbstractSecurityConfig {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
@Configuration
public class CustomOauth2Config extends Oauth2Config{
}
}
4 实现ClientDetailsService
接口,完成自己的认证逻辑
完成自定义实现后,按照名字customClientDetailsService
向spring中注入一个ClientDetailsService
实例
【特别注意】在用户未按照(4)中的步骤配置自己的授权逻辑时,组件会默认进行一个缺省实现。在缺省实现的情况下,用户能使用任意用户名配合密码(12345678)进行登录
# 四种授权方式
# 密码模式
POST /oauth/token HTTP/1.1
Host: oauth2.yishuifengxiao.com
Authorization: Basic fdsfdsfdsfds
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=johndoe&password=A3ddj3w
在请求中,各参数的含义如下
- grant_type:表示授权类型,此处的值固定为"password",必选项。
- username:表示用户名,必选项。
- password:表示用户的密码,必选项。
- scope:表示权限范围,可选项。
- Authorization: 请求头参数 ,值是 clientId:clientSecret 经过 base64 编码后的值
下面是一个响应的例子
{
"access_token": "BDF867DE69F05143C709",
"token_type": "bearer",
"refresh_token": "d7cda8fb15714209a9f9f3b039a0034f",
"expires_in": 43199,
"scope": "read write trust",
"client_id": "yishui"
}
# 客户端模式
POST /oauth/token HTTP/1.1
Host: oauth2.yishuifengxiao.com
Authorization: Basic fdsfdsfdsfds
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
在本请求中,各参数的含义如下
- grant_type:表示授权类型,此处的值固定为"client_credentials",必选项。
- Authorization: 请求头参数 ,值是 clientId:clientSecret 经过 base64 编码后的值
下面是一个响应的例子
{
"access_token": "BDF867DE69F05143D3BF",
"token_type": "bearer",
"expires_in": 43199,
"scope": "read write trust",
"client_id": "yishui"
}
同密码模式相比,客户端模式的响应中缺少了
refresh_token
参数
# 授权码模式
授权码模式首先需要保证 spring security 的登陆功能正常可用。只有开启 spring security 的登陆功能可用,才能开启授权码功能。
先访问一下请求
GET /oauth/authorize?response_type=code&client_id=yishui&state=xyz
&redirect_uri=http://demo.yishuifengxiao.com/demo HTTP/1.1
Host: oauth2.yishuifengxiao.com
在本请求中,各参数的含义如下:
- code:表示授权码,必选项。该码的有效期应该很短,通常设为 10 分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端 ID 和重定向 URI,是一一对应关系。
- state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
- client_id: 用户的 client_id
在进行此请求时,假如用户没有登录,spring security 会进行拦截,因此需要用户先进行登录。
在正常情况下,访问以上请求会被重定向到
http://demo.yishuifengxiao.com/demo?code=fsfsdf &state=xyz
服务器回应客户端的 URI,包含以下参数:
- code:表示授权码,必选项。该码的有效期应该很短,通常设为 10 分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端 ID 和重定向 URI,是一一对应关系。
- state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。
通过上面的请求得到了code
以后,用户需要使用下面请求获取到授权码
POST /oauth/token HTTP/1.1
Host: oauth2.yishuifengxiao.com
Authorization: Basic fdsfdsfdsfds
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=fsfsdf
&redirect_uri=demo.yishuifengxiao.com/demo
在本请求中,各参数的含义如下:
- grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"。
- code:表示上一步获得的授权码,必选项。
- redirect_uri:表示重定向 URI,必选项,且必须与 A 步骤中的该参数值保持一致。
- client_id:表示客户端 ID,必选项。
# 简化模式
GET /oauth/authorize?response_type=token&client_id=yishui&state=xyz
&redirect_uri=http://demo.com/demo HTTP/1.1
Host: server.example.com
在本请求中,各参数的含义如下:
- response_type:表示授权类型,此处的值固定为"token",必选项。
- client_id:表示客户端的 ID,必选项。
- redirect_uri:表示重定向的 URI,可选项。
- scope:表示权限范围,可选项。
- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
# 刷新 token
POST /oauth/token HTTP/1.1
Host: oauth2.yishuifengxiao.com
Authorization: Basic fdsfdsfdsfds
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=sdff
请求中个参数的含义:
- granttype:表示使用的授权模式,此处的值固定为"refresh_token",必选项。
- refresh_token:表示早前收到的更新令牌,必选项。
- scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。
# access_token 使用
通过 前面的方法获取到 access_token 之后,一般有两种使用方法
- 将 access_token 做为请求参数携带在 url 参数上
http://demo.yishuifengxiao.com/user/123?access_token=获得到的access_token
- 将 access_token 做为请求参数放在请求头中
在所有需要授权的请求的请求头里都携带上参数 Authorization=Bearer 获得到的 access_token
在通用组件中,由于对access_token
进行了深度处理,因此用户可以access_token
通过易水工具包里的 DES 工具饭解析出 token 里携带的信息。
在解密时需要使用的密钥由【安全管理】中设置的yishuifengxiao.security.secret-key
属性值决定。
下面是一个 access_token
的解密信息示例
{
"username": "yishui",
"clientId": "admin",
"roles": [
"ROLE_USER",
"admin"
],
"grantType": "password"
}
解密信息的各参数的解释:
- username: 用户登录时使用到的用户名(在客户端模式下该值为空)
- clientId: 用户登录时使用的 clientId(在简化模式下该值为空)
- roles: 此登录用户拥有的角色(即此用户的 authorities)
- grantType:access_token 对应的授权类型
由
access_token
反解析出用户信息仅限于本组件,原生的 oauth2 的access_token
不支持此功能
在本组件中,每次访问后都会重置令牌的自动过期时间
# 异常转换
在本组件中,默认由Auth2ResponseExceptionTranslator
实现 oauth2 中的异常转换。如果需要自定义异常转换过程,只需要在 spring 的上下文中注入一个名为auth2ResponseExceptionTranslator
的WebResponseExceptionTranslator
实例即可。
示例代码如下:
@Bean("auth2ResponseExceptionTranslator")
public WebResponseExceptionTranslator auth2ResponseExceptionTranslator() {
return new Auth2ResponseExceptionTranslator();
}
# token存储策略
在组件中,token的存储方式有三种
- 在系统中没有配置reids时默认存储在内存中,由
InMemoryTokenStore
负责处理 - 在系统中配置有redis连接时默认存储在redis里,由
RedisTokenStore
负责处理 - 自定义token存储策略
在自定义token存储策略,只需要向spring 上下文中注入一个名为 tokenStore
的TokenStore
实例即可。
示例如下:
@Bean("tokenStore")
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
# token提取器
在默认情况下,系统使用的是组件提供的默认token提取器从请求中提取出来token信息。从请求中提取token的策略如下:
- 先从请求头中获取名为
Authorization
的参数的值,且该值以Bearer
开头 - 如果上步骤中没有获取到信息,就从请求参数中提取名为
access_token
的参数的值 - 如果上步骤中还没有获取到信息,就从session中获取获取名为
yishuifengxiao.session.token
的session的值
如果用户想要自定义token提取方式,只需要向spring security中注入一个名为tokenExtractor
的TokenExtractor
实例即可。
/**
* 自定义token提取器
*
* @return
*/
@Bean("tokenExtractor")
@ConditionalOnMissingBean(name = "tokenExtractor")
public TokenExtractor tokenExtractor() {
return new CustomTokenExtractor();
}
# token前置验证
为了在BasicAuthenticationFilter
之前进行前置验证,组件向系统中注入了一个名为tokenEndpointAuthenticationFilter
的OncePerRequestFilter
实例,用来进行前置验证。用户如果有自定义要求,可以注入自定义处理策略。示例代码如下:
/**
* 配置一个过滤器,用于在oauth2中提前验证用户名和密码以及clientId
*
* @param handlerProcessor 协助处理器
* @param propertyResource 资源管理器
* @param securityHelper 安全信息处理器
* @param clientDetailsService ClientDetailsService
* @param passwordEncoder 加密器
* @param oauth2Properties oauth2扩展支持属性配置
* @return 过滤器
*/
@Bean("tokenEndpointFilter")
@ConditionalOnMissingBean(name = "tokenEndpointFilter")
public Filter tokenEndpointFilter(HandlerProcessor handlerProcessor, PropertyResource propertyResource,
SecurityHelper securityHelper,
@Qualifier("customClientDetailsService") ClientDetailsService clientDetailsService,
PasswordEncoder passwordEncoder, Oauth2Properties oauth2Properties) {
TokenEndpointFilter tokenEndpointFilter = new TokenEndpointFilter(handlerProcessor, propertyResource,
securityHelper, clientDetailsService, passwordEncoder, oauth2Properties);
return tokenEndpointFilter;
}
# token工具类
该工具的全路径如下
com.yishuifengxiao.common.oauth2.support.OAuth2TokenUtil
@Autowired
private OAuth2TokenUtil oAuth2TokenUtil;
token工具类主要是在oauth2中用于进行各种token操作。token工具类的主要方法的签名如下:
/**
* 根据token的值移除存储的登录token
*
* @param tokenValue token的
* @return 移除成功返回为true,否则为false
*/
public boolean removeToken(String tokenValue);
/**
* 根据请求里token信息移除存储的登录token <br/>
* token 的提取方式参见 TokenExtractor
*
* @param request HttpServletRequest
* @return 移除成功返回为true,否则为false
*/
public boolean removeToken(HttpServletRequest request) ;
/**
* 根据认证信息生成token
*
* @param request
* @param username 用户名
* @param clientId
* @param clientSecret 原始终端密码
* @param grantType 授权类型,默认为 custome
* @return
*/
public OAuth2AccessToken createToken(HttpServletRequest request, String username, String clientId,
String clientSecret, String grantType) ;
/**
* 根据认证信息生成token
*
* @param request
* @param username 用户名
* @param clientId
* @param clientSecret 原始终端密码
* @param grantType 授权类型,默认为 custome
* @return
*/
public OAuth2AccessToken createToken(HttpServletRequest request, String username, String clientId,
String grantType) ;
/**
* 根据认证信息生成token
*
* @param request
* @param username 用户名
* @param clientDetails 终端信息
* @param clientSecret 原始终端密码
* @param grantType 授权类型,默认为 custome
* @return
*/
public OAuth2AccessToken createToken(HttpServletRequest request, String username, ClientDetails clientDetails,
String grantType);
/**
* 根据认证信息生成token 【请求头中必须包含basic信息】
*
* @param request
* @param authentication spring security登陆成功后的认证信息
* @param grantType 授权类型
* @return
* @throws IOException
*/
public OAuth2AccessToken createToken(HttpServletRequest request, Authentication authentication, String grantType)
throws IOException ;
/**
* 根据认证信息和客户端信息生成token
*
* @param authentication spring security登陆成功后的认证信息
* @param clientId clientId
* @param clientSecret 原始终端密码
* @param grantType 授权类型,默认为custome
* @return OAuth2AccessToken
*/
public OAuth2AccessToken createToken(Authentication authentication, String clientId, String clientSecret,
String grantType);
/**
* 根据认证信息和客户端id信息生成token </br>
* <b>注意此方法不会校验终端密码,一定要在可信环境下使用</b>
*
* @param authentication spring security登陆成功后的认证信息
* @param clientId clientId
* @param grantType 授权类型,默认为custome
* @return OAuth2AccessToken
*/
public OAuth2AccessToken createToken(Authentication authentication, String clientId, String grantType) ;
/**
* 根据认证信息生成token
*
* @param authentication spring security登陆成功后的认证信息
* @param clientDetails 终端登录成功后的认证信息
* @param grantType 授权类型,默认为custome
* @return
*/
public OAuth2AccessToken createToken(Authentication authentication, ClientDetails clientDetails, String grantType);
/**
* 根据终端id获取到终端的完整信息
*
* @param clientId
* @return
*/
private ClientDetails extracted(String clientId) ;
/**
* 根据用户生成UsernamePasswordAuthenticationToken
*
* @param request
* @param username 用户名
* @return
*/
private UsernamePasswordAuthenticationToken extracted(HttpServletRequest request, String username) ;
# 常见问题
# 1 访问 /oauth/token 提示404
这是因为未开启oauth2功能,需要按照【快速启动】章节的说明配置好oauth2功能即可。