OAuth2.0 及 Spring 实现

用户头像
心平气和
关注
发布于: 2020 年 07 月 15 日



OAuth2.0是OAuth协议的延续版本,但不向前兼容OAuth 1.0(即完全废止了OAuth1.0)。OAuth 2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。同时为Web应用,桌面应用和手机,和起居室设备提供专门的认证流程。2012年10月,OAuth 2.0协议正式发布为RFC 6749

百度百科



一般来说需要开放能力给第三方的时候需要用到认证,注意是第三方,内部系统一般来说不用,因为内部我们一般有自己的用户中心或者登录中心,这些系统可以用来做认证,而为什么开放给第三方需要认证呢,主要有以下几方面的原因:

1、安全



2、重用

如果每个系统开放出去都需要自己实现一套认证流程,那系统的交互效率太低,所以会有一套统一的认证过程,来和第三方交互,一般来说这些在网关上用的比较多。



三、OAuth2.0流程及概念介绍



在介绍流程之前,先举1个场景,假如有个社交网站www.xxx.com想集成qq登录,用户在社交网站注册后会存放其个人数据,通过 www.xx.com/personal可以访问用户的头像等信息(实际url可能是网关的),我们也忽略参数等细节。



大概有以下几类角色:



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来跑下流程。







新增认证服务器代码




@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);
}

}



再加入认证逻辑,这里先写死,简单点:

ublic 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;
}
}



再配置用户信息,也是先写死:


@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;
}

}



再新加一个控制器, 这个表示我们的要保护的资源



@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访问资源



中间需要登录,输入上面定义的账号和密码

edward

123



想持续关注新的技术,就关注我的公众号吧~





发布于: 2020 年 07 月 15 日 阅读数: 159
用户头像

心平气和

关注

欢迎关注公众号:程序员升级之路 2018.03.06 加入

还未添加个人简介

评论

发布
暂无评论
OAuth2.0及Spring实现