如何通过PKCE模式访问受Oauth2保护的资源
本文用来说明vue如何使用PKCE模式访问受oauth2保护的资源。所用技术栈包括spring boot, spring security和vue.
如何通过PKCE模式访问受Oauth2保护的资源
本文用来说明vue如何使用PKCE模式访问受oauth2保护的资源。Demo示例代码在github里
概念介绍
什么是PKCE? 这里有详细的介绍。PKCE是Proof Key for Code Exchange的缩写。简单来说就是获取Token过程中,利用两个数值比较来验证请求者是否是最终用户,防止攻击者盗用Token。 它提高了公有云环境下的单页面运用或APP获取Token的安全性,也能应用在私有云环境里。我们可利用它来实现SSO。
具体步骤简单描述如下:
第一步:先随机生成一个32位长的code_verifier,然后运用hash算法s256加密得到一个code_challenge。js算法实现可参考这里;
第二步:客户端向oauth2认证服务器申请认证码code, 并带着code_challenge;
第三步:客户端利用返回的code和code_verifier来向oauth2服务器申请token;
第四步:客户端通过在header里加bearer token就能访问受限资源了;
Demo使用说明
代码分两部分:authentication和html。authentication是oauth2认证和资源服务,利用spring security实现; html利用spring boot+thymleaf+vue来模拟一个客户端。
-
准备
需1.8(含)以上的JDK和Maven。 将Maven的命令mvn配到机器环境的path里,同时在环境里配置JAVA_HOME指向JDK所在目录。 -
启动
-
运行认证和资源服务
cd authentication
mvn -DskipTests clean package
java -jar target/authentication-0.0.1-SNAPSHOT.jar
- 运行客户端
cd html
mvn -DskipTests clean package
java -jar target/ui-0.0.1-SNAPSHOT.jar
- 执行
authentication服务端口是30000, 客户端端口是30010. 在chrome浏览器输入http://127.0.0.1:30010, 即进入客户端。如下图所示:
“非认证点击” 按钮演示未获token访问接口的效果,上面会有出错提示。
“认证点击” 按钮演示PKCE获取token后访问接口的效果,如成功,上面会有一段json信息。
【备注】获取code需表单认证,用户名和密码为tom和sonia 这在authentication里配置
这两个按钮调用的是authentication服务里的接口,该接口受oauth2保护。接口定义如下:
//这是在authentication的com.ghy.vo.authentication.controller.DemoController里
@RequestMapping(value = "/res/showData")
public Object showData2(){
Map<String,String> map = new HashMap<String,String>();
map.put("t1","this is test1");
map.put("t2","this is test2");
return map;
}
//这是在authentication的资源配置,在com.ghy.vo.authentication.config.OAuth2Server里将/res/*定义为受oauth2保护
@Configuration
@EnableResourceServer
protected class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
......
@Override
public void configure(HttpSecurity http) throws Exception {
final String[] urlPattern = {"/res/**"};
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.requestMatchers().antMatchers(urlPattern)
.and()
.authorizeRequests()
.antMatchers(urlPattern)
.access("#oauth2.hasScope('read') and hasRole('ROLE_USER')")
;
}
下图是成功的显示:
如图所示,标红的地方表明了获取code, token和调用接口的network过程。
实现过程
authentication工程
认证服务
见OAuth2Server类,通过@EnableAuthorizationServer定义认证服务器,将oauth2认证模式"authorization_code"加载到内存里。如下:
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("vo_client_id")
.secret("{noop}")
.redirectUris("http://127.0.0.1:30010")
.authorizedGrantTypes("authorization_code")
.scopes("read")
.autoApprove(true)
;
}
引入PKCE认证模式
在OAuth2Server类里进行配置,引入相关服务和tokenGranter,如下:
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(tokenStore)
.authorizationCodeServices(new PkceAuthorizationCodeServices(endpoints.getClientDetailsService(), passwordEncoder))
.tokenGranter(tokenGranter(endpoints));
PKCE实现
见pkce包里
- CodeChallengeMethod类用来codeVerifier和codeChallenge比较
- PkceAuthorizationCodeServices类用于调度
- PkceAuthorizationCodeTokenGranter类用于从request请求中获取token所需信息
- PkceProtectedAuthentication类定义PKCE认证对象信息
全局跨域访问和Token调用设置
见CrosFilter类
@Configuration
@Order(5)
public class CrosFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Accept, Origin, Content-Type, Authorization");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true");
String method = request.getMethod();
if ("OPTIONS".equals(method)) {
response.setStatus(HttpStatus.OK.value());
} else {
chain.doFilter(req, res);
}
}
}
跨域设置遇到的一些坑:曾配过CorsConfig类和SecurityConfiguration类的corsConfigurationSource()方法,都没效果,特别是从client调用token时都不行。将代码放在Demo里,是为了记录过程。已将注解注释掉,代码没有使用。
最后经过摸索,最终发现用filter方式是可行的。另外一种方式是用spring拦截器模式。Demo里没有描述。我在下面参考列出来,供学习。
filter实现参见CrosFilter类。分两部分,第一部分是跨域设置和对Header等控制,第二部分是当请求是options时,则返回200。这是配合axios获取token时用的
html工程
主要实现见test.html, 其中
- hash加密算法通过crypto-js.min.js来引入实现
- pkce.js用来生成code_vefivier和code_challenge
- 获取token,通过Qs实现options的简单调用
- "认证点击"按钮的调用流程如下图,主要用到了js的异步回调技术;
可改进的地方
- 认证信息从缓存挪到DB或redis里
- client端还要加logout等清理token等方法
参考
更多推荐
所有评论(0)