一、说明
oauth2 授权模式一共有四种,即隐式授权模式、授权码授权模式、密码授权模式和客户端授权模式。
这里仅对授权码授权模式所包含的流程和接口做说明和整理。
具体的概念和源码解读,资料有很多,可以自行去搜索学习。
二、流程说明
假设有这样一个场景:现有A系统和B系统,A系统想要使用B系统的账号来做三方登录,那么A系统就必须要获取B系统的授权,以便拿到B系统的用户信息。(可以参考微信账号的三方登录场景)
想要实现以上场景就可以使用oauth2的授权码模式,具体流程参考下图:
其中clientId、clientSecret、redirectUri等信息涉及到的表是:oauth_client_details
三、所涉及到的接口以及代码整理
以下会对接口做简要说明,最重要的是整理涉及到的自定义配置
想要了解具体代码逻辑实现,可以重点看一下 AuthorizationEndpoint 这个类的源码
① /oauth/authorize 接口 GET请求
该接口对应上图的第一步,即传递参数,获取B系统的登录界面。
oauth2的登录界面很丑,很难满足实际使用场景,可以通过在认证服务器配置中使用自定义的登录页面替换。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.requestMatchers().antMatchers("/login*","/oauth/authorize").and()
.authorizeRequests()
.anyRequest().permitAll()
.and()
.formLogin()
//使用自定义的登录页面
.loginPage("/login.html")
.loginProcessingUrl("/login");
}
注:如果同时存在认证服务器配置和资源服务牌配置的话,需要设置认证服务器配置的优先级高于资源服务器配置。
/**
* 认证服务器配置
*/
@Configuration
@Order(1) //设置该配置的优先级
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
...
}
/**
* 资源服务器配置
*/
@Configuration
@EnableResourceServer
public class UnifiedResourceServerConfig extends ResourceServerConfigurerAdapter {
...
...
}
② /oauth/confirm_access 接口
该接口会重定向到授权页面,同样,oauth2的授权界面很难满足实际使用场景,可以通过配置替换为自定义的授权页面。
//替换为自定义的授权页面
endpoints.pathMapping("/oauth/confirm_access", "/custom/confirm_access");
//自定义授权页面
@Controller
@SessionAttributes("authorizationRequest")
public class GrantController {
@RequestMapping("/custom/confirm_access")
public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest");
ModelAndView view = new ModelAndView();
view.setViewName("grant");
view.addObject("clientId", authorizationRequest.getClientId());
view.addObject("scopes",authorizationRequest.getScope());
return view;
}
}
<!-- 自定义授权页面 -->
<form method="post" action="/oauth/authorize">
<input type="hidden" name="user_oauth_approval" value="true">
<div th:each="item:${scopes}">
<input type="radio" th:name="'scope.'+${item}" value="true" hidden="hidden" checked="checked"/>
</div>
<button class="btn" type="submit"> 同意/授权</button>
</form>
授权后的信息存放在 oauth_approvals 表中。
具体源码可以查看 JdbcApprovalStore 类,该类中的sql默认是使用 Mysql 语法,同样可以通过自定义替换成别的数据库语法。
③ /oauth/authorize 接口 POST请求
该接口会生成code码,最终会重定向到A系统的redirectUrl地址,并将生成的code码以参数的形式传递到A系统。
生成的code码保存在 oauth_code 表中。
具体源码可以查看 JdbcAuthorizationCodeServices 类。
可以在代码中自定义code码生成规则
public class UnifiedAuthorizationCodeServices extends JdbcAuthorizationCodeServices {
public UnifiedAuthorizationCodeServices(DataSource dataSource) {
super(dataSource);
}
private RandomValueStringGenerator generator = new RandomValueStringGenerator(13);
@Override
public String createAuthorizationCode(OAuth2Authentication authentication) {
String code = generator.generate();
store(code, authentication);
return code;
}
}
配置代码
@Bean
public AuthorizationCodeServices authorizationCodeServices() {
return new UnifiedAuthorizationCodeServices(dataSource);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// token 携带额外信息
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer()));
endpoints
.userDetailsService(userDetailsService)
.tokenStore(tokenStore())
.tokenServices(defaultAuthorizationServerTokenServices())
.authenticationManager(authenticationManager)
.accessTokenConverter(getAccessTokenConverter())
.exceptionTranslator(oauth2ResponseExceptionTranslator)
//自定义sql查询语句
.approvalStore(jdbcApprovalStore())
//自定义授权码生成规则
.authorizationCodeServices(authorizationCodeServices());
//替换为自定义的授权页面
endpoints.pathMapping("/oauth/confirm_access", "/custom/confirm_access");
}
④ /oauth/token 接口 authorization_code模式
该接口对应图中,A系统访问B系统,传递clientId,clientSecret,code,redirectUri等参数换取B系统的accessToken和refreshToken。
有了B系统token,就可以通过token调用B系统的接口获取B系统的用户信息了。