Spring Security OAuth2 专题
1.概述
首先了解一下Oauth2,什么是Oauth2 ?
Oauth2是一种协议规范,他的实现有很多种,Spring Security Oauth2 是对他的一种实现。
Oauth2的认证方式有授权码模式,简单模式,密码模式,客户端模式等等,还可以自己去实现其他模式。
可以通过这个链接去了解更多关于Oauth的概念:http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
在这里主要讲授权服务,授权服务主要有四大核心功能:
- 客户端授权关系管理
- 接收用户或客户端登录请求,生成accessToken
- token存储
- token校验
2.授权服务配置
授权服务的几个关键类
-
@EnableAuthorizationServer
启动授权服务,并引入
AuthorizationServerEndpointsConfiguration
及AuthorizationServerSecurityConfiguration
两个配置类 -
AuthorizationServerEndpointsConfiguration/AuthorizationServerEndpointsConfigurer
授权服务Ebdpoints配置类
-
AuthorizationServerSecurityConfiguration/AuthorizationServerSecurityConfigurer
授权服务访问授权配置类
-
AuthorizationServerConfigurerAdapter(AuthorizationServerConfigurer)
授权服务配置适配器,开发人员可以定义实现类来完成对授权服务的配置
开发定义的配置列表:
-
ClientDetailsService:ClientDetails信息加载实现类。
-
TokenStore:token管理服务。
-
TokenEnhancer:token信息的额外信息处理。
-
PasswordEncoder: clientDetail 信息里的client_secret字段加解密器。
-
-
ClientDetailsServiceConfigurer
客户端配置类
2.1.AuthorizationServerConfigurerAdapter (授权服务配置适配器)
-
AuthorizationServerConfigurerAdapter
是 AuthorizationServer配置的适配类。 -
AuthorizationServerConfigurerAdapter分别有三个方法:
//定义客户端详细信息服务的配置器。可以初始化客户端详细信息 public void configure(ClientDetailsServiceConfigurer clients) //定义令牌端点上的安全约束。 public void configure(AuthorizationServerEndpointsConfigurer endpoints) //定义授权和令牌端点以及令牌服务。 public void configure(AuthorizationServerSecurityConfigurer security)
-
OAuth2AuthorizationServerConfiguration
是AuthorizationServerConfigurerAdapter
的系统默认实现,开发人员可以参考这个类的实现。 -
开发如何自定义实现如下:
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { /** * 客户端信息访问配置 * 配置clientDetails授权方式, * * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { } /** * 访问端点配置 * @param endpoints */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { } /** * 授权服务安全配置 * @param security */ @Override public void configure(AuthorizationServerSecurityConfigurer security) { } }
下面再详细讲解三个配置。
-
类关系图
2.2.ClientDetailsServiceConfigurer (配置客户端详细信息)
这个配置主要是注入ClientDetailsService实例,其它地方通过ClientDetailsServiceConfigurer调用开发配置的ClientDetailsService。系统提供两个ClientDetailsService实现类:JdbcClientDetailsService、InMemoryClientDetailsService。
/**
* 客户端信息访问配置
* 配置clientDetails授权方式,
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 1.这里用于测试,客户端信息从内存中获取
clients.inMemory()
.withClient("client") // client_id
.secret("secret")// client_secret,客户端密码
.authorizedGrantTypes("password")// 该client允许的授权类型
.scopes("app") // 允许的授权范围
.autoApprove(true);//登录后绕过批准询问
// 2.客户端信息从数据库获取
// clients.jdbc(dataSource);
// 3.这是自己实现ClientDetailsService
// clients.withClientDetails(clientDetailsService);
}
2.3.AuthorizationServerEndpointsConfigurer (访问端点配置)
AuthorizationServerEndpointsConfigurer
是一个转载器,将Endpoints所有相关的配置类:(AuthorizationServer、TokenServices、TokenStore、ClientDetailsService、UserDetailsService)
配置实例:
/**
* 访问端点配置
* @param endpoints
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager) // 认证管理器
.tokenStore(new InMemoryTokenStore()) // token存储、系统实现了 内存存储、Redis存储、数据库存储、JWTtoken
.userDetailsService(userDetailsService) //用户信息获取方式,密码模式必须要
.tokenEnhancer(new JwtAccessTokenConverter()) //令牌增强,可以设置生成不同类型的token,如jwt
.tokenGranter(tokenGranter(endpoints)); //配置grant_type模式,如果不配置则默认使用密码模式、简化模式、验证码模式以及刷新token模式,如果配置了只使用配置中,默认配置失效
}
2.4.AuthorizationServerSecurityConfigurer (授权服务安全配置)
AuthorizationServerSecurityConfigurer
继承SecurityConfigurerAdapter.也就是一个 Spring Security安全配置提供给AuthorizationServer去配置AuthorizationServer的端点(/oauth/**)的安全访问规则、过滤器Filter。
/**
* 授权服务安全配置
* 配置:安全检查流程,用来配置令牌端点(Token Endpoint)的安全与权限访问
* 默认过滤器:BasicAuthenticationFilter
* 1、oauth_client_details表中clientSecret字段加密【ClientDetails属性secret】
* 2、CheckEndpoint类的接口 oauth/check_token 无需经过过滤器过滤,默认值:denyAll()
* 对以下的几个端点进行权限配置:
* /oauth/authorize:授权端点
* /oauth/token:令牌端点
* /oauth/confirm_access:用户确认授权提交端点
* /oauth/error:授权服务错误信息端点
* /oauth/check_token:用于资源服务访问的令牌解析端点
* /oauth/token_key:提供公有密匙的端点,如果使用JWT令牌的话
*
* @param security
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.allowFormAuthenticationForClients()
.passwordEncoder(new BCryptPasswordEncoder())
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
2.5.完整授权配置示例
/**
* @author lizhichao
* @description 授权服务配置
* @date 2021/10/13 17:03
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Autowired
@Qualifier("UserDetailsServiceImpl")
private UserDetailsService userDetailsService;
/**
* 客户端信息访问配置
* 配置clientDetails授权方式,
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 1.这里用于测试,客户端信息从内存中获取
clients.inMemory()
.withClient("client") // client_id
.secret("$2a$10$xox4JztwJISO9eWlj85.YO5ugx3RjVQYXXQ.TyACcjseiyyPeb8Q6")// client_secret
.authorizedGrantTypes("password","authorization_code","implicit", "refresh_token")// 该client允许的授权类型
.scopes("app") // 允许的授权范围
.autoApprove(true) //登录后绕过批准询问
.redirectUris("https://www.baidu.com");
// 2.客户端信息从数据库获取
// clients.jdbc(dataSource);
// 3.这是自己实现ClientDetailsService
// clients.withClientDetails(clientDetailsService);
}
/**
* 访问端点配置
* @param endpoints
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints//.authenticationManager(authenticationManager) // 认证管理器
.tokenStore(new InMemoryTokenStore()) // token存储
.userDetailsService(userDetailsService) //用户信息获取方式,密码模式必须要
.tokenEnhancer(new JwtAccessTokenConverter()) //令牌增强,可以设置生成不同类型的token,如jwt
.tokenGranter(tokenGranter(endpoints)); //配置grant_type模式,用户可以进行扩展,如果不配置则默认使用密码模式、简化模式、验证码模式以及刷新token模式,如果配置了只使用配置中,默认配置失效
}
private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
List<TokenGranter> list = new ArrayList<>();
//这里配置密码模式、刷新token模式、授权码模式、简化模式
list.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
list.add(new RefreshTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
list.add(new AuthorizationCodeTokenGranter(endpoints.getTokenServices(),endpoints.getAuthorizationCodeServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
list.add(new ImplicitTokenGranter(endpoints.getTokenServices(),endpoints.getClientDetailsService(),endpoints.getOAuth2RequestFactory()));
return new CompositeTokenGranter(list);
}
/**
* 授权服务安全配置
* 配置:安全检查流程,用来配置令牌端点(Token Endpoint)的安全与权限访问
* 默认过滤器:BasicAuthenticationFilter
* 1、oauth_client_details表中clientSecret字段加密【ClientDetails属性secret】
* 2、CheckEndpoint类的接口 oauth/check_token 无需经过过滤器过滤,默认值:denyAll()
* 对以下的几个端点进行权限配置:
* /oauth/authorize:授权端点
* /oauth/token:令牌端点
* /oauth/confirm_access:用户确认授权提交端点
* /oauth/error:授权服务错误信息端点
* /oauth/check_token:用于资源服务访问的令牌解析端点
* /oauth/token_key:提供公有密匙的端点,如果使用JWT令牌的话
*
* @param security
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.allowFormAuthenticationForClients() //允许表单提交
.passwordEncoder(new BCryptPasswordEncoder()) //密码加密方式
.tokenKeyAccess("permitAll()") // 令牌访问权限
.checkTokenAccess("isAuthenticated()"); //token校验访问权限
}
}
2.6.安全配置(WebSecurityConfig)
安全配置主要是配置请求访问权限、定义认证管理器、密码加密配置。
@Order(2)
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("UserDetailsServiceImpl")
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.csrf().disable();
}
/**
* 身份验证管理器配置
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
/**
* 密码编码器
*
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
}
3.资源服务配置
这里的工作是将鉴权管理器AuthorizationManager配置到资源服务器、请求白名单放行、无权访问和无效token的自定义异常响应。配置类基本上都是约定俗成那一套,核心功能和注意的细节点通过注释说明。
3.1.AuthorizationManager (鉴权管理器)
/**
*
*鉴权管理器
*/
@Component
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> mono , AuthorizationContext authorizationContext) {
ServerHttpRequest request = authorizationContext.getExchange().getRequest();
// 1.跨域的预请求直接放行
if (request.getMethod() == HttpMethod.OPTIONS){
return Mono.just(new AuthorizationDecision(true));
}
// 2.token 为空直接拒绝
String token = request.getHeaders().getFirst(AuthConstants.JWT_TOKEN_HEADER);
if (token == null || token.length() == 0){
return Mono.just(new AuthorizationDecision(false));
}
// 3.这里可以做权限拦截,比如根据不同的权限,来拦截路由,我这里就没做啦!
return mono.filter(Authentication::isAuthenticated)
.map(e->new AuthorizationDecision(true))
.defaultIfEmpty(new AuthorizationDecision(false));
}
}
3.2.资源配置
这里配置用什么oauth2资源服务
@Component
@EnableWebFluxSecurity
public class ResourceServerConfig {
@Autowired
private AuthorizationManager authorizationManager;
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
//资源服务配置服务 可以选择 jwt 和 opaqueToken
http.oauth2ResourceServer().opaqueToken();
//授权配置
http.authorizeExchange()
.anyExchange().access(authorizationManager)
.and()
.csrf().disable();
return http.build();
}
}
4.完整代码示例
这里做下完整的代码示例、其模块包含 授权服务 、网关。
代码见GitHub :https://github.com/769306079/gateway-oauth-demo
Q.E.D.