一、什么是OAuth2.0
引用百度的解释:
OAuth2.0是OAuth协议的延续版本,但不向前兼容OAuth 1.0(即完全废止了OAuth1.0)。OAuth 2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。同时为Web应用,桌面应用和手机,和起居室设备提供专门的认证流程。2012年10月,OAuth 2.0协议正式发布为RFC 6749 百度百科
说简单点就是用来认证的。
二、什么场景下需要使用OAuth2.0
一般来说需要开放能力给第三方的时候需要用到认证,注意是第三方,内部系统一般来说不用,因为内部我们一般有自己的用户中心或者登录中心,这些系统可以用来做认证,而为什么开放给第三方需要认证呢,主要有以下几方面的原因:
1、安全
内部调用用户中心,邮箱、手机号等信息一般都可以拿到,而开放给第三方的时候我们需要做好安全处理,以防第三方的原因造成个人数据泄露等问题。
2、重用
如果每个系统开放出去都需要自己实现一套认证流程,那系统的交互效率太低,所以会有一套统一的认证过程,来和第三方交互,一般来说这些在网关上用的比较多。
有兴趣可以看下如何集成qq登录,它的实现就是遵循OAuth2.0标准。
三、OAuth2.0流程及概念介绍
在介绍流程之前,先举1个场景,假如有个社交网站www.xxx.com想集成qq登录,用户在社交网站注册后会存放其个人数据,通过 www.xx.com/personal可以访问用户的头像等信息(实际url可能是网关的),我们也忽略参数等细节。
1、OAuth2.0角色
大概有以下几类角色:
A、Client
有的也叫第三方应用程序,指想集成资源访问的应用。
说的有点绕,拿上面的例子来说www.xxx.com 指的就是Client。
B、Resource Owner
资源所有者,一般指用户;
因为我们需要访问用户的邮箱、手机号信息,这些是属于用户的,需要征得用户同意,所以用户是资源所有者。
C、User Agent
用户代理,指浏览器。
D、Authorization server
认证服务器,即服务提供商专门用来处理认证的服务器。
E、Resource server
资源服务器,即服务提供商存放用户生成的资源的服务器。
回到上面的例子,假如我们要访问用户邮箱,即通过 www.xxx.com/personal这个接口获取,那资源服务器就是www.xxx.com所在服务器。
在部署上它可以与认证服务器在同一台服务器,也可以在不同的服务器。
2、OAuth2.0几种模式
A、授权码模式(Authorization code Grant)
贴上官方的图
步骤如下:
(1)用户访问Client,即www.xxx.com,后者将前者导向认证服务器,即graph.qq.com/oauth2.0/show;
(B)用户选择是否给予客户端授权;
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)的请求。
B、简化模式(Implicit Grant)
同授权码模式相对,区别在于没有获取授权码的步骤。
C、密码模式(Resource Owner Password Credentials Grant)
(1)用户向客户端(www.xxx.com)提供用户名和密码。
(2)客户端(www.xxx.com)将用户名和密码发给认证服务器(open.qq.com),向后者请求令牌。
(3)认证服务器确认无误后,向客户端提供访问令牌。
D、客户端模式(client credentials)
这是一种最简单的模式,只要client请求,即在www.xxx.com后台任务发起请求,我们就将AccessToken发送给它。
四、Spring实现
因为篇幅的原因,不准备写太多代码,我们以一个小的demo来跑下流程。
在idea中创建一个springbot的项目,注意选择spring web和spring security,
新增认证服务器代码
代码语言:javascript复制@Configuration
@EnableAuthorizationServer
public class ConfigAdapter extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource myDataSource;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
}
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
/**
* 数据源
* @return
*/
public DataSource dataSource(){
return myDataSource;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("client-id")
.secret("client-secret")
.scopes("read", "write")
.authorizedGrantTypes("password", "refresh_token", "code", "authorization_code") //对应response_type是否有权限
.redirectUris("http://www.baidu.com")
.authorities("user:view");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
}
再加入认证逻辑,这里先写死,简单点:
代码语言:javascript复制public class MyAuthenticationManager implements AuthenticationManager {
/*
授权过程
*/
public Authentication authenticate(Authentication authentication) throws AuthenticationException{
String userName, password;
userName = (String)authentication.getPrincipal();
password = (String)authentication.getCredentials();
if ((userName == "edward") && (password == "123")){
Authentication res = new UsernamePasswordAuthenticationToken(authentication.getPrincipal()
, authentication.getPrincipal());
return res;
}
return null;
}
}
再配置用户信息,也是先写死:
代码语言:javascript复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
MyAuthenticationProvider myAuthenticationProvider() {
return new MyAuthenticationProvider();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("edward").password("123").authorities("USER")
.and()
.withUser("user_2").password("123456").authorities("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception{
//http.regexMatcher("/image/. ").authorizeRequests().anyRequest().authenticated();
http.authorizeRequests().antMatchers("/post/**", "/oauth/**", "/login/**").permitAll()
.anyRequest().authenticated()
.and().formLogin().permitAll()
;
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
@Bean
@Override
protected UserDetailsService userDetailsService(){
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("edward").password("123").authorities("USER").build());
manager.createUser(User.withUsername("user_2").password("123456").authorities("USER").build());
return manager;
}
}
再新加一个控制器, 这个表示我们的要保护的资源
代码语言:javascript复制@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("hello")
public String hello(){
return "hello user";
}
}
然后在浏览器访问授权码模式的几个URL:
1、获取token
http://localhost:8080/oauth/authorize?response_type=code&client_id=client-id&redirect_uri=http://www.baidu.com
2、根据code获取token
http://localhost:8080/oauth/token?grant_type=authorization_code&code=Dx5tnU&client_id=client-id&client_secret=client-secret&redirect_uri=http://www.baidu.com
这一步会跳转到www.baidu.com,并且返回一个accessToken
3、根据accessToken访问资源
http://localhost:8080/user/hello?accessToken=123
中间需要登录,输入上面定义的账号和密码
edward
123